java中如何高效判斷兩個容器是否有相同元素(時間複雜度為O(1))
很多時候我們需要知道兩個容器是否存在相同的元素,這裡以電商網站中的優惠活動為例。比如我們想知道一個商品是否參與了滿件折扣活動(幾件幾折),已知一個商品可能會參與多種優惠活動,比如滿減活動(滿多少減多少)、臨期降價等優惠活動。我們知道每一種優惠活動都可以建立一張優惠券,只是有些優惠券是直接和商品掛鉤的,有些優惠券必須要使用者領取了才能參與活動,這裡我們只討論直接和商品掛鉤的優惠券。上面的問題就可以這麼理解,一個商品包含多個優惠券id的集合C,假設滿件折扣優惠券id集合D已知,那麼我們怎麼判斷集合C中是否存在滿件折扣的優惠券呢,也就是集合C和集合D是否存在相同的元素?沒錯,C.retainAll(D)就可以計算出兩個集合是否有交集。那我們來開下retainAll原始碼,程式碼如下:
public boolean retainAll(Collection<?> c){
boolean modified = false;
Iterator<E> e = iterator();
while (e.hasNext()){
if (!c.contains(e.next())){
e.remove();
modified = true;
}
}
return modified;
}
我們可以看到retainAll還是遍歷了整個集合D,也就是時間複雜度為O(n),如果我們只是簡單的求兩個集合的交集,那麼這個時間複雜度我們是可以接受的,但是如果我們有多個集合C呢,我把上面的問題改一下:使用者購買了多個商品(可能上千個商品),那麼我們需要從這上千個商品中挑出參與滿件折扣優惠活動的商品單獨處理,通過以上我們知道一個商品包含一個優惠券id集合,那麼多個商品就應該包含多個優惠券的集合,也就是集合裡面套集合。如果我們還通過以上方法來計算多個集合與集合D是否有交集的話,那麼時間複雜度就是O(m*n^2)(假設集合個數為m,m為常數,每個集合的大小是n)。那還有沒有其他的方法使這個演算法的時間複雜度為O(m)呢,即O(1)?
我們只需要獲取和集合D有交集的集合,並不需要遍歷每個元素從而獲取相同的元素是哪些。所以遍歷每個元素從而獲取是否和集合D有交集的演算法簡直就是浪費時間!這裡我們使用java中不允許重複元素的集合來使時間複雜度達到O(1),即A集合的大小+B集合的大小和AB集合大小的比較來判斷A、B兩個集合是否存在交集。程式碼如下:
private List<Set<Long>> getUnionCollection(List<Set<Long>> C, Set<Long> D) {
List<Set<Long>> unionCollection = Lists.newArrayList();
int DSize = D.size();
for(Set<Long> cSet : C){
if(CollectionUtils.isEmpty(cSet)){
continue;
}
Set<Long> joinSet = Sets.newHashSet();
int cSetSize = cSet.size();
joinSet.addAll(D);
joinSet.addAll(cSet);
int joinSize = joinSet.size();
//兩個容器大小之和大於合併後的容器大小,說明有重複元素
if((cSetSize + DSize) > joinSize){
unionCollection.add(cSet);
}
}
return unionCollection;
}
利用Set集合中元素不重複的特點,集合D、cSet以及joinSet三個集合都不存在重複元素,所以如果D的大小+cSet的大小>joinSet的大小的話,那麼一定存在重複的元素。