今天在刷leetCode時,碰到了一個題是這樣的。
給定一個整數數組,判斷是否存在重復元素。
如果任何值在數組中出現至少兩次,函數返回 true。如果數組中每個元素都不相同,則返回 false。
看到這個題的第一時間,就想到了利用集合ArrayList來存儲,並且判斷。
代碼:
class Solution { public boolean containsDuplicate(int[] nums) { Set<Integer> list=new HashSet<>(); for (int i=0;i<nums.length;i++){ if(list.contains(nums[i])){ return true; }else { list.add(nums[i]); } } return false; } }
然而,當數據量足夠大的時候,會提示超出時間限制
后來又用了指針遍歷數組,時間復雜度勉強通過,也不盡人意
最后又用到了HashSet,時間復雜度已經很好了
public static boolean containsDuplicate(int[] nums) { Set<Integer> set=new HashSet<>(); for (int i=0;i<nums.length;i++){ if(set.contains(nums[i])){ return true; }else { set.add(nums[i]); } } return false; }
這個時候。引發了我對這兩者的思考,
這道題不但在考你的基礎算法實現,還涉及到算法效率優化問題。也就是必須要關注算法的時間復雜度。
既然如此,就趁這個機會加深一下ArrayList與HashSet元素查找的時間復雜度區別,實際上就是底層的實現區別。
ArrayList本質就是通過數組實現的,查找一個元素是否包含要用到遍歷,時間復雜度是O(n)
而HashSetHashSet的查找是通過HashMap的KeySet來實現的,判斷是否包含某個元素的實現,時間復雜度是O(1)
ArrayList判斷是否包含某個元素的源碼實現:
public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) //從頭遍歷 if (o.equals(elementData[i])) return i; } return -1; }
HashSet判斷是否包含某個元素的源碼實現:
public boolean contains(Object o) { return map.containsKey(o); } public boolean containsKey(Object key) { return getNode(hash(key), key) != null; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //直接通過hash確定元素位置,不用從頭遍歷 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))//部分情況下可能會繼續遍歷鏈表定位 return e; } while ((e = e.next) != null); } } return null; }