今天去面試了一家公司,記錄下一些問題。
如何改善下面代碼?
List<Integer> outerList = new ArrayList<>(10000);
List<Integer> innerList = new ArrayList<>(10000);
for (int i = 0; i < outerList.size(); i++) {
for (int j = 0; j < innerList.size(); j++) {
if (outerList.get(i).equals(innerList.get(j))) {
break;
}
}
}
這段代碼有個性能問題,就是在outerList和innerList的數量非常大的情況下,比如outerList有m個,innerList有n個,那麼在最差的情況下循環數量是
簡單地說,這段代碼的算法複雜度是
在outerList和innerList的數量都是10000的情況下,執行結果:
Use Double Iterator to get the same object
Start Loop
End Loop, use 147 ms
第一種方案是對outerList和innerList排序,然後和上面一樣的做法
outerList.sort(Comparator.comparingInt(o -> o));
innerList.sort(Comparator.comparingInt(o -> o));
for (int i = 0; i < outerList.size(); i++) {
for (int j = 0; j < innerList.size(); j++) {
if (outerList.get(i).equals(innerList.get(j))) {
break;
}
}
}
這個方法顯然是非常失敗,複雜度還是
Use Sorted Double Iterator to get the same object
Start Loop
End Loop, use 243 ms
第二種方案是借用HashMap,把outerList裏的值放到map裏,然後藉助map.get(key)的
Map<Integer, Object> map = new HashMap<>(outerList.size());
for (int i = 0; i < outerList.size(); i++) {
map.put(outerList.get(i), null);
}
for (int i = 0; i < innerList.size(); i++) {
if (map.containsKey(innerList.get(i))) {
break;
}
}
執行結果
Use Map to get the same object
Start Loop
End Loop, use 3 ms
第三種方案我想着藉助Set的去重特性來優化,TreeSet和HashSet的實現不同,所以效果也不同
Set<Integer> set = new HashSet<>(outerList);
for (int i = 0; i < innerList.size(); i++) {
if (set.contains(innerList.get(i))) {
break;
}
}
Set<Integer> set = new TreeSet<>(outerList);
for (int i = 0; i < innerList.size(); i++) {
if (set.contains(innerList.get(i))) {
break;
}
}
執行結果:
Use TreeSet to get the same object
Start Loop
End Loop, use 11 ms
Use HashSet to get the same object
Start Loop
End Loop, use 3 ms
最後加了個parallelStream的處理方式,這裏應該可以優化
outerList.parallelStream().forEach(innerList::contains);
執行時間
Use Stream to get the same object
Start Loop
End Loop, use 54 ms
總結下各方案的耗時
方法 | 耗時 |
---|---|
原始方法 | 147ms |
List排序 | 243ms |
Map方法 | 3ms |
TreeSet方法 | 11ms |
HashSet方法 | 3ms |
ParallelStream | 54ms |
HashSet本身是用HashMap實現的,所以二者的時間幾乎一樣。