文章標題

今天去面試了一家公司,記錄下一些問題。

如何改善下面代碼?

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個,那麼在最差的情況下循環數量是mn ,最優的情況是循環m 次,平均下來是

i=mnmi=n(2m+n)2

簡單地說,這段代碼的算法複雜度是O(n2)
在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;
        }
    }
}

這個方法顯然是非常失敗,複雜度還是O(n2) ,沒有任何優化,而且隨着outerList的值的增大,在innerList後半段查到的可能性加大,增加了innerList的循環的次數,增加了時間,另外還增加了排序的時間。執行結果:

Use Sorted Double Iterator to get the same object 
Start Loop 
End Loop, use 243 ms

第二種方案是借用HashMap,把outerList裏的值放到map裏,然後藉助map.get(key)的O(1) 複雜度來提高效率。

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實現的,所以二者的時間幾乎一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章