各位看官早安午安晚安呀
如果您覺得這篇文章對您有幫助的話
歡迎您一鍵三連,小編盡全力做到更好
歡迎您分享給更多人哦
今天我們來學習Map/Set的底層實現
目錄
問題一:hash會出現負數?數組越界
一:什么是二叉搜索樹?
1.1:創建一個二叉搜索樹
1.2.移除一個節點:
1.3.二叉搜索樹的缺陷:
1.4.引出map和set以及和紅黑樹之間的關系
二:關于搜索我們一般用到的方式
2.1.靜態查找(就只進行查找不進行其他的操作)
2.2.但是現實中的查找大多數都是動態查找:
2.3?模型
三:Map
3.2.Map常用方法說明
3.3.關于Map方法的注意事項
四:Set(去重很好)
4.2.Set注意事項
五:我們日常的比較
六:哈希表
6.1:哈希沖突
6.2:哈希沖突不可避免
6.2.1:沖突-避免-哈希函數設計
6.2.2.?沖突-避免-負載因子調節(重點掌握)
6.3:哈希沖突解決:
6.3.1.閉散列
6.3.2:開散列/哈希桶(特別重要,很好用)
6.4:泛型寫法
一定要重寫equals方法!!!
6.5:總結:性能分析
7. OJ練習
7.1:只出現一次的數字
7.2:隨機鏈表的復制
7.3:寶石與石頭
7.4:壞鍵盤打字
7.5:輸出前K個高頻單詞
問題一:hash會出現負數?數組越界
問題二:為什么會死循環?
問題三:第一個練習:萬一 一個數字出現了三次呢?
一:什么是二叉搜索樹?
- 若它的左子樹不為空,則左子樹上所有節點的值都小于根節點的值
- 若它的右子樹不為空,則右子樹上所有節點的值都大于根節點的值
- 它的左右子樹也分別為二叉搜索樹
1.1:創建一個二叉搜索樹
public class BinarySearchTree {static class TreeNode {public int val;public TreeNode left;public TreeNode right;public TreeNode(int val) {this.val = val;}}public TreeNode root;public boolean search(int key){TreeNode cur = root;if(root == null){return false;}while(cur != null){if(cur.val > key){cur = cur.left;} else if (cur.val <key) {cur = cur.right;}else{return true;}}return false;}public boolean insert(int key){TreeNode node = new TreeNode(key);if(root == null){// 如果根就是的話,直接添加了,然后return true;root = node;return true;}TreeNode cur = root;TreeNode parent = cur;//賦值while(cur != null){if(cur.val < key){parent = cur; //這一步一定要提前cur = cur.right;} else if (cur.val >key) {parent = cur;cur = cur.left;}else{return false;}}if(parent.val < key){ //你就說為什么cur == null了把它小于的話(cur在該添加的位置上面等于了null)parent.right = node;}else{parent.left = node;}return true;}
1.2.移除一個節點:
要思考的問題:
代碼實現:
public void remove(int val){TreeNode cur = root;//要移除這個節點,首先我要先遍歷二叉搜索樹找到這個節點TreeNode parent = null;if(root == null){return;}while(cur != null)if(cur.val < val) { parent = cur;cur = cur.right;}else if(cur.val > val){parent = cur;cur = cur.left;}else{//要刪除的節點,找到了removeTreeNode(cur,parent);}}private void removeTreeNode(TreeNode cur,TreeNode parent) {if(cur.left == null){if(cur.val > parent.val){//這一步就純純的是判讀那cur走到了parent的左邊還是右邊,parent.right = cur.right;//你這一點都不知道,我滴媽你讓parent的左邊接力還是右邊?}else{parent.left = cur.right;}}else if(cur.right == null){ //cur的右邊空了if(cur.val > parent.val){//和上面一樣parent.right = cur.left;}else{parent.left = cur.left;}}else{ // cur 的兩邊都不為空TreeNode target = cur.right; //準備TreeNode parentTarget = null;while(target.left != null){parentTarget = target;target = target.left;}cur.val = target.val;if(parentTarget != null){parentTarget.left = target.right;}else{cur.right = target.right;}}}
1.3.二叉搜索樹的缺陷:
插入和刪除操作都必須先查找,查找效率代表了二叉搜索樹中各個操作的性能。
對有 n 個結點的二叉搜索樹,若每個元素查找的概率相等,則二叉搜索樹平均查找長度是結點在二叉搜索樹的深度的函數,即結點越深,則比較次數越多。但對于同一個關鍵碼集合,如果各關鍵碼插入的次序不同,可能得到不同結構的二叉搜索樹:
- 最優情況下,二叉搜索樹為完全二叉樹,其平均比較次數為:
- 最差情況下,二叉搜索樹退化為單支樹,其平均比較次數為:N/2(這個時候效率已經沒了)
1.4.引出map和set以及和紅黑樹之間的關系
二:關于搜索我們一般用到的方式
2.1.靜態查找(就只進行查找不進行其他的操作)
1. 直接遍歷,時間復雜度為 O(N) ,元素如果比較多效率會非常慢2. 二分查找,時間復雜度為log(N), 但搜索前必須要求序列是有序的
2.2.但是現實中的查找大多數都是動態查找:
1. 根據姓名查詢考試成績2. 通訊錄,即根據姓名查詢聯系方式3. 不重復集合,即需要先搜索關鍵字是否已經在集合中
2.3?模型
比如:有一個英文詞典,快速查找一個單詞是否在詞典中,快速查找某個名字在不在通訊錄中
比如:統計文件中每個單詞出現的次數,統計結果是每個單詞都有與其對應的次數: < 單詞,單詞出現的次數 >
三:Map
Map官方文檔
Map是一個接口類,該類沒有繼承自Collection,該類中存儲的是<K,V>結構的鍵值對(這個鍵值對往大了點說就是一個內部類)(譬如樹里面的節點里面存儲了左右孩子節點),并且K一定是唯一的,不能重復(那這個<K,V>)
3.1.關于Map.Entry<K, V>(很重要)
首先Entry<K,V>是Map接口里面的一個內部接口),自然,TreeMap里面的Entry<K,V>這個類也是實現了Entry這個內部接口
如圖:TreeMap里面的Entry<K,V>這個類也是實現了Entry這個內部接口
關于Map.Entry<K,V>(在Map里面的內部接口)這個類就相當于二叉樹里面的節點
總的來說:Map.Entry<K, V> 是Map內部實現的用來存放<key, value>鍵值對映射關系的內部類,該內部類中主要提供了<key, value>的獲取,value的設置以及Key的比較方式
注意:Map.Entry<K,V>并沒有提供設置Key的方法
3.2.Map常用方法說明
解釋:在 Java 中,當你嘗試將一個對象賦值給一個基本數據類型時,實際上會發生自動拆箱(unboxing)操作,但這之前還有一個更關鍵的檢查點:空值檢查。
在這個例子中,
map.get("lihua")
?返回一個?Integer
?對象(或者?null
,如果鍵?"lihua"
?不存在)。但是,當你嘗試將這個?Integer
?對象賦值給一個?int
?類型的變量?val
?時,Java 編譯器會插入一個自動拆箱操作。然而,在自動拆箱之前,Java 運行時環境會檢查?
Integer
?對象是否為?null
。如果對象是?null
,那么嘗試進行拆箱操作(即將?Integer
?轉換為?int
)是不合法的,因為基本數據類型?int
?不能是?null
。在這種情況下,Java 不會拋出類型轉換異常(ClassCastException
),而是會拋出一個空指針異常(NullPointerException
)。類型轉換異常(
ClassCastException
)通常發生在嘗試將一個對象強制轉換為不兼容類型的對象時。但在這個例子中,問題不在于類型的不兼容,而在于嘗試將一個空引用(null
)用作基本數據類型的值。
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;public class Test {public static void main(String[] args) {Map<String,Integer> map = new TreeMap<>();
//1.put 和 get(Object key) 返回 key 對應的 valuemap.put("zhangsan",18);map.put("zhangsan",20);//會進行更新,key值不會重復map.put("lisi",18);map.put("wangWu",50);//int val = map.get("lihua");返回的是一個空指針異常而不是類型轉換異常,很有講究的//這個時候就可以用這個方法map.getOrDefault("lihua",20);// 沒有key的話就相當于一次put,(key是lihua)(val是20)
//3.(keyset):Set<K> keySet() 返回所有 key 的不重復集合Set<String> set = map.keySet(); //經過排序的從大到小for (String s: set) {System.out.print(s + " ");}//或者System.out.println(set);
//4:Set<K> keySet() 返回所有 key 的不重復集合Collection<Integer> collection = map.values();//也是有順序的,一般都是按照key的進行排序System.out.println(collection);System.out.println("==============");
//5:Set<Map.Entry<K, V>> entrySet() 返回所有的 key-value 映射關系Set<Map.Entry<String,Integer>> entries = map.entrySet();for (Map.Entry<String,Integer> entry:entries) {System.out.println("key:" + entry.getKey() + " val:" + entry.getValue());}//或者直接打印也行System.out.println(entries);System.out.println("===========");}
結果:
3.3.關于Map方法的注意事項
- 1. Map是一個接口,不能直接實例化對象,如果要實例化對象只能實例化其實現類TreeMap或者HashMap。
- 2. Map中存放鍵值對的Key是唯一的,value是可以重復的
- 3. 在TreeMap中插入鍵值對時,key不能為空,否則就會拋NullPointerException異常(在我看來放入TreeMap的key需要進行比較利用compareTo方法,(這個時候null調用compareTo方法就會報空指針異常)所以不能插入key為空的情況)(TreeMap是基于紅黑樹實現的,它要求所有的key都必須實現Compare接口,或者通過構造函數傳入Comparator。value可以為空。但是HashMap的key和value都可以為空。
- 4. Map中的Key可以全部分離出來,存儲到Set中(Set<K> keySet())來進行訪問(因為Key不能重復)。
- 5. Map中的value可以全部分離出來,存儲在Collection的任何一個子集合中(value可能有重復)。
- 6. Map中鍵值對的Key不能直接修改,value可以修改,如果要修改key,只能先將該key刪除掉,然后再來進行重新插入。
四:Set(去重很好)
Set的官方文檔
4.2.Set注意事項
注意:
- 1. Set是繼承自Collection的一個接口類
- 2. Set中只存儲了key,并且要求key一定要唯一
- 3. TreeSet的底層是使用Map來實現的,其使用key與Object的一個默認對象作為鍵值對插入到Map中的
- 4. Set最大的功能就是對集合中的元素進行去重
- 5. 實現Set接口的常用類有TreeSet和HashSet,還有一個LinkedHashSet,LinkedHashSet是在HashSet的基礎
- 上維護了一個雙向鏈表來記錄元素的插入次序。
- 6. Set中的Key不能修改,如果要修改,先將原來的刪除掉,然后再重新插入
- 7. TreeSet中不能插入null的key(和TreeMap的解釋一樣,都是要進行比較的),HashSet可以。
五:我們日常的比較
函數:數學函數!!!給一個x就能得出一個y,時間復雜度就是O(1)
至此,我們成功引入哈希表!!!
六:哈希表
插入元素:我們通過對關鍵碼(Key)的運算,計算出該元素存儲的位置,然后直接放在該位置
搜索元素:對元素的Key進行同樣的計算,把求得的函數值當做元素的存儲位置,在結構中按此位置取元素比較,若關鍵碼相等,則搜索成功
該方式即為哈希 ( 散列 ) 方法, 哈希方法中使用的轉換函數稱為哈希 ( 散列 ) 函數,構造出來的結構稱為哈希表 (Hash Table)( 或者稱散列表 )例如:數據集合 {1 , 7 , 6 , 4 , 5 , 9} ;哈希函數設置為: hash(key) = key % capacity ; capacity 為存儲元素底層空間總的大小
但我再加一個為44的元素呢?結果就是完蛋,和4這個元素你倆撞上了
6.1:哈希沖突
上述的兩個元素撞上了,就是你倆通過一個哈希函數然后計算出的哈希值一模一樣,導致你倆放的位置一樣,這樣就是大名鼎鼎的——哈希沖突
具體解釋:
對于兩個數據元素的關鍵字 和 (i != ) ,有k i != kj? ,但有: Hash( ki) == Hash(kj) ,即: 不同關鍵字通過相同哈 希哈數計算出相同的哈希地址,該種現象稱為哈希沖突或哈希碰撞 。把具有不同關鍵碼而具有相同哈希地址的數據元素稱為 “ 同義詞 ”
6.2:哈希沖突不可避免
- 設計合理的哈希函數
- 擴容(和負載因子有關):底層數組盡量可以根據放入元素的多少進行擴容(底層容量改變了,沖突也會盡量少一點)
6.2.1:沖突-避免-哈希函數設計
? ? ? ?1.? 哈希函數的定義域必須包括需要存儲的全部關鍵碼,而如果散列表允許有m個地址時,其值域必須在0到m-1之間(我是數組我要連續(*^▽^*))
? ? ? 2.? 哈希函數計算出來的地址能均勻分布在整個空間中
? ? ? 3.哈希函數應該比較簡單
1. 直接定制法 --( 常用 )取關鍵字的某個線性函數為散列地址: Hash ( Key ) = A*Key + B 優點:簡單、均勻 缺點:需要事先知道關 鍵字的分布情況 使用場景:適合查找比較小且連續的情況面試題:只出現一次 的字符2. 除留余數法 --( 常用 )設散列表中允許的地址數為m ,取一個不大于 m ,但最接近或者等于 m 的質數 p 作為除數,按照哈希函數:Hash(key) = key% p(p<=m), 將關鍵碼轉換成哈希地址3. 平方取中法 --( 了解 )假設關鍵字為 1234 ,對它平方就是 1522756 ,抽取中間的 3 位 227 作為哈希地址; 再比如關鍵字為 4321 ,對它平方就是18671041 ,抽取中間的 3 位 671( 或 710) 作為哈希地址 平方取中法比較適合:不知道關鍵字的分 布,而位數又不是很大的情況4. 折疊法 --( 了解 )折疊法是將關鍵字從左到右分割成位數相等的幾部分 ( 最后一部分位數可以短些 ) ,然后將這幾部分疊加求和,并按散列表表長,取后幾位作為散列地址。折疊法適合事先不需要知道關鍵字的分布,適合關鍵字位數比較多的情況5. 隨機數法 --( 了解 )選擇一個隨機函數,取關鍵字的隨機函數值為它的哈希地址,即 H(key) = random(key), 其中 random 為隨機數函數。通常應用于關鍵字長度不等時采用此法6. 數學分析法 --( 了解 )設有 n 個 d 位數,每一位可能有 r 種不同的符號,這 r 種不同的符號在各位上出現的頻率不一定相同,可能在某些位上分布比較均勻,每種符號出現的機會均等,在某些位上分布不均勻只有某幾種符號經常出現。可根據散列表的大小,選擇其中各種符號分布均勻的若干位作為散列地址。例如
假設要存儲某家公司員工登記表,如果用手機號作為關鍵字,那么極有可能前 7 位都是 相同的,那么我們可以選擇后面的四位作為散列地址,如果這樣的抽取工作還容易出現 沖突,還可以對抽取出來的數字進行反轉( 如1234改成 4321) 、右環位移 ( 如 1234 改成 4123) 、左環移位、前兩數與后兩數疊加 ( 如 1234 改成 12+34=46) 等方法。數字分析法通常適合處理關鍵字位數比較大的情況,如果事先知道關鍵字的分布且關鍵字的若干位分布較均 勻的情況注意:哈希函數設計的越精妙,產生哈希沖突的可能性就越低,但是無法避免哈希沖突
6.2.2.?沖突-避免-負載因子調節(重點掌握)
6.3:哈希沖突解決:
一般是這兩種方法
- 二次探測和線性探測(閉散列): 讓你們兩個沖突的元素,第一個放在計算出來的位置,然后再給另一個元素再設計一個函數進行放置(也稱之為二次探測),? 不進行二次探測的話,也可以把另外一個元素進行線性探測(既然這個位置已經有人了,那我就放在下一個空的位置(反正小編不想這么干))
- 數組 + 鏈表(開散列/哈希桶)(小編很喜歡這個):既然你倆計算出來相同的位置了,你倆都別委屈了,都來吧(
)
等等方法………………(接下倆小編為大家講解這幾個方法)
6.3.1.閉散列
我們來想想
線性探測的缺點:?線性探測會導致產生沖突的數據堆積在一塊,這與其找下一個空位置有關系(一個一個挨著找,這種情況很正常)而二次探測可以避免這個問題
解決二:二次探測:
6.3.2:開散列/哈希桶(特別重要,很好用)
開散列/哈希桶(數組 + 鏈表)
雖然這樣可行,但是萬一沖突的元素太多了怎么辦呢?(我還不是要遍歷鏈表?)
好好好,那我就把你鏈表變成二叉樹()
這個思想就是不斷地把(大集合的搜索問題轉化為小集合的搜索問題)
6.3.3:開散列/哈希桶的實現
// key-value 模型
public class HashBucket {public static class Node {private int val;private int key;public Node next;//這里一定要注意,你如果在這里上來就構造了一個根節點的public Node(int key, int val) {this.key = key;this.val = val;}}private static final double LOAD_FACTOR = 0.75;//負載因子//根本不需要根節點,我底層是數組進行實現的,要你沒有用//public Node root;public Node[] array = new Node[5]; //我這個就是HsahMap的底層實現,開散列:數組 + 鏈表private int usedSize; //一般只要一個數據結構底層運用了數組,一般都會有usedSize去記錄有效元素public boolean search(int key) { //這個要判斷val嗎?int index = key % array.length;Node cur = array[index];// 我直接計算出這個key應該在的位置,然后在數組的這個位置進行查找while (cur != null) {if (cur.key == key) {return true;}cur = cur.next;}return false;}public void put(int key, int val) {int index = key % array.length;Node cur = array[index];while (cur != null) { // 先遍歷一遍這個鏈表,判讀這個key在不在里面,如果在的話,就更新val值if (cur.key == key) {cur.val = val;}cur = cur.next;}//走到這里說明不在這里,直接進行頭插Node node = new Node(key,val);node.next = array[index];array[index] = node;usedSize++;if(usedSize * 1.0/ array.length > LOAD_FACTOR){ //這里要乘 1.0revise(array);}}private void revise(Node[] array) { //這里要是有參數的話,下面的array,一定要加this啊!!!!!!!!!!!//首先讓數字進行擴容//最后再返回數組Node[] tmpArray = Arrays.copyOf(array,2*array.length);//先遍歷一遍原來的數組,再在每個數組元素上遍歷鏈表for (int i = 0; i < array.length; i++) {Node cur = array[i];while(cur != null){//計算出新的下標,再進行頭插int newIndex = cur.key % tmpArray.length;//這個節點就是cur其實沒必要再多定義了/*Node node = new Node(cur.key,cur.val);node.next = tmpArray[newIndex];tmpArray[newIndex] = node;cur = cur.next;
*///你們是一樣的道理Node curNext = cur.next;cur.next = tmpArray[newIndex];tmpArray[newIndex] = cur;cur =curNext;}}array = tmpArray;// 這里一定要this啊!!!!!!!!!!!!!// 除非上面沒有參數}public int get(int key){int index = key % array.length;Node cur = array[index];// 我直接計算出這個key應該在的位置,然后在數組的這個位置進行查找while (cur != null) {if (cur.key == key) {return cur.val;}cur = cur.next;}return -1;}
}
Test(測試)
public class Test {public static void main(String[] args) {HashBucket hashBucket = new HashBucket();hashBucket.put(1,1);hashBucket.put(2,2);hashBucket.put(3,3);hashBucket.put(4,4);hashBucket.put(5,5);//就這里i = 1下標出問題了System.out.println("===============");System.out.println(hashBucket.get(1));System.out.println(hashBucket.get(2));System.out.println(hashBucket.get(5));System.out.println(hashBucket.get(9));}
}
我這里給大家提出一個問題,如果我上述有revise(array)并且后面array = tmpArray,(我也沒加this,為什么最后會死循環呢?)
大家可以去運行一下,為什么最后會死循環?
6.4:泛型寫法
class Person{String name;public Person(String name) {this.name = name;}//一定要重寫equals方法,因為我要看看你一開始在不在里面,相同名字的lihua@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(name, person.name);}//這個也是@Overridepublic int hashCode() {return Objects.hash(name);}
}public class HashBucket1 <K,V>{public class Node<K,V>{ //為什么不能是靜態的?(靜態的不依賴于類,所以說我根本不知道K,V是誰?private K key;private V val;private Node<K,V> next;//這里一定要注意,你如果在這里上來就構造了一個根節點的public Node( K key, V val) {this.key = key;this.val = val;}}private Node[] array ;private static final double LOAD_FACTOR = 0.75;//負載因子public HashBucket1() {this.array = new Node[10];}private int usedSize;public void put(K key, V val){int hash = key.hashCode();//還能是負的?int index = Math.abs(hash % array.length);Node<K,V> cur = array[index];//先找一遍看有沒有,沒有直接頭插法while(cur != null){if(cur.key.equals(key)){ //所以說這個key一定要重寫equals方法cur.val = val;return;}cur = cur.next;}// 到這里就肯定沒有了,沒有直接頭插法Node<K,V> node = new Node<>(key,val);node.next = array[index];array[index] = node;usedSize++;// 判斷負載因子大不大if(usedSize*1.0 / array.length > LOAD_FACTOR){revise();}}private void revise() {Node<K,V> [] tmpArray = Arrays.copyOf(array,2*array.length);for (int i = 0; i < array.length; i++) {Node<K,V> cur = array[i];while(cur != null){Node<K,V> curNext = cur.next;//找到新下標直接進行插入int newIndex = Math.abs(cur.hashCode() % tmpArray.length);cur.next = tmpArray[newIndex];tmpArray[newIndex] = cur;cur = curNext;}}this.array = tmpArray;}public V get(K key){int index = key.hashCode() % array.length;Node<K,V> cur = array[index];while(cur != null){if(cur.key == key){return cur.val;}cur = cur.next;}return null;}public boolean search(K key){int index = key.hashCode() % array.length;Node<K,V> cur = array[index];while(cur != null){if(cur.key == key){return true;}cur = cur.next;}return false;}
}
Test測試
public class TestHashBucket1 {public static void main(String[] args) {Person person = new Person("zhangSan");Person person1 = new Person("zhangSan");Person person2 = new Person("zhangSan");HashBucket1<Person,Integer> hashBucket1= new HashBucket1();//這個key一定要重寫equals方法(不然俺咋判斷,我寫的哈希表里面有沒有這個元素hashBucket1.put(person,18);hashBucket1.put(person1,18);hashBucket1.put(person2,18);System.out.println("============");}
}
一定要重寫equals方法!!!
寫了:
沒寫:
6.5:總結:性能分析
- ?HashMap 和 HashSet 即 java 中利用哈希表實現的 Map 和 Set
- ?java 中使用的是哈希桶方式解決沖突的
- ?java 會在沖突鏈表長度大于一定閾值后,將鏈表轉變為搜索樹(紅黑樹)
?java 中計算哈希值實際上是調用的類的 hashCode 方法,進行 key 的相等性比較是調用 key 的 equals 方法。所以如果要用自定義類作為 HashMap 的 key 或者 HashSet 的值, 必須重寫 ?hashCode 和 equals 方 法。而且要做到 equals 相等的對象, hashCode 一定是一致的。
7. OJ練習
7.1:只出現一次的數字
只出現一次的數字
方法一:我們沒學哈希表之前(直接全部異或就好了)
方法二:
class Solution {public int singleNumber(int[] nums) {Set<Integer> set = new HashSet<>();
//一邊進去,相同的出來for(int i = 0; i < nums.length; i++){if(set.contains(nums[i])){set.remove(nums[i]);}else{set.add(nums[i]);}}
//如果包含就是你了(只出現過一次)for(int i = 0; i < nums.length; i++){if(set.contains(nums[i])){return nums[i];}}return -1;}
}
7.2:隨機鏈表的復制
隨機鏈表的復制
import java.util.Map;
class Solution {public Node copyRandomList(Node head) {Node cur = head;Map<Node,Node> map = new HashMap<>();while(cur != null){Node node = new Node(cur.val);map.put(cur,node); //幫下面的鏈接上,通過HashMapcur = cur.next;//node = node.next; 這樣寫肯定不對,因為我每次都要new一個Node對象,就鏈接不上了}cur = head;while(cur != null){map.get(cur).random = map.get(cur.random);map.get(cur).next = map.get(cur.next);cur = cur.next;}return map.get(head);}
}
7.3:寶石與石頭
寶石與石頭
class Solution {public int numJewelsInStones(String jewels, String stones) {Set<Character> set = new HashSet<>();for(int i = 0; i<jewels.length(); i++){set.add(jewels.charAt(i)); // 是add而不是put}int count = 0;for(int i = 0; i<stones.length(); i++){char ch = stones.charAt(i);if(set.contains(ch)){count++;}}return count;}
}
7.4:壞鍵盤打字
壞鍵盤打字
public static void find(String s1,String act){Set<Character> set = new HashSet<>();for (char ch: act.toUpperCase().toCharArray()) {set.add(ch);}Set<Character> set2 = new HashSet<>();for (char ch: s1.toUpperCase().toCharArray()) {if(! set.contains(ch)){set2.add(ch);}}for (Object o:set2.toArray()) {System.out.println(o);}}public static void main(String[] args) {String s1 = "abc def 7 abc";String s2 = "abc";find(s1,s2);}
需要掌握,String的toUpperCase()(轉換大寫),toCharArray()(轉換成字符數組)
以及Set的toArray()將里面的元素轉換成數組
7.5:輸出前K個高頻單詞
輸出前K個高頻單詞
public List<String> topKFrequent(String[] words, int k) {// 一個哈希表,統計所有單詞出現的個數Map<String,Integer> map = new HashMap<>();for(String word : words){if( !map.containsKey(word)){map.put(word,1);}else{int count = map.get(word);map.put(word,++count);}}//建立小根堆,重寫比較方法// 這里的Entry<>記得前面加上Map,因為他是Map里面的內部類,所有的都要加PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {@Overridepublic int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {if((o1.getValue() - o2.getValue()) == 0 ){return o2.getKey().compareTo(o1.getKey());}return o2.getValue() - o1.getValue();}});for (Map.Entry<String,Integer> entry : map.entrySet()) {Map.Entry<String,Integer> top = minHeap.peek();if(minHeap.size() <= k){minHeap.offer(entry);}else{// 如果你倆的出現的頻率相同,讓字母的ASCII碼值進行比較大的進來if(top.getValue().equals(entry.getValue())){if(top.getKey().compareTo(entry.getKey()) > 0){minHeap.poll();minHeap.offer(entry);}}else{// 下一個元素的val大,讓下一個元素進來if(entry.getValue().compareTo(top.getValue()) > 0){minHeap.poll();minHeap.offer(entry);}}}}List<String> ret = new ArrayList<>();for (int i = 0; i < k; i++) {Map.Entry<String,Integer> top = minHeap.poll();ret.add(top.getKey());}Collections.reverse(ret);return ret;}
上述就是哈希碰撞攻防戰——深入淺出Map/Set的底層實現
的全部內容了,哈希碰撞還有很多我們的未解之謎,讓未來的我們一起去解決吧~~~
能看到這里相信您一定對小編的文章有了一定的認可。
有什么問題歡迎各位大佬指出
歡迎各位大佬評論區留言修正~~