思路:若是用單個哈希表,刪除元素以後哈希表中會出現許多空隙,getRandom沒法作到等機率返回了。必須是連續的存儲才能出現等機率。node
因此須要創建2個哈希表,其中一個存儲 key,index ,這些不一樣的key之間是無序的,另外一個存儲index,key,這些index也是無序的,可是是等機率的算法
public class RandomPool { public static class Pool<K> { private HashMap<K, Integer> keyIndexMap; private HashMap<Integer, K> indexKeyMap; private int size; public Pool() { this.keyIndexMap = new HashMap<K, Integer>(); this.indexKeyMap = new HashMap<Integer, K>(); this.size = 0; } public void insert(K key) { if (!this.keyIndexMap.containsKey(key)) { this.keyIndexMap.put(key, this.size); this.indexKeyMap.put(this.size++, key); } } public void delete(K key) { if (this.keyIndexMap.containsKey(key)) { //獲得要刪除key的索引 int deleteIndex = this.keyIndexMap.get(key); //獲得indexKeyMap最後一個key,同時size減去1 int lastIndex = --this.size; K lastKey = this.indexKeyMap.get(lastIndex); //更新哈希值,把lastkey和deleteIndex關聯 this.keyIndexMap.put(lastKey, deleteIndex); this.indexKeyMap.put(deleteIndex, lastKey); //刪除刪除的key this.keyIndexMap.remove(key); this.indexKeyMap.remove(lastIndex); } } public K getRandom() { if (this.size == 0) { return null; } int randomIndex = (int) (Math.random() * this.size); // 0 ~ size -1 return this.indexKeyMap.get(randomIndex); } } public static void main(String[] args) { Pool<String> pool = new Pool<String>(); pool.insert("zuo"); pool.insert("cheng"); pool.insert("yun"); System.out.println(pool.getRandom()); System.out.println(pool.getRandom()); System.out.println(pool.getRandom()); System.out.println(pool.getRandom()); System.out.println(pool.getRandom()); System.out.println(pool.getRandom()); } }
題目二:數據結構
小島數量dom
一個矩陣中只有0和1兩種值,每一個位置均可以和本身的上、下、左、右 四個位置相連,若是有一片1連在一塊兒,這個部分叫作一個島,求一個 矩陣中有多少個島?函數
常規思路:this
深度優先搜索,每搜完一次,小島數量加一,這裏的深搜經過 ‘感染’函數,每個島均可以感染上下左右相鄰的島。spa
public class Islands { public static int countIslands(int[][] m) { if (m == null || m[0] == null) { return 0; } int N = m.length; int M = m[0].length; int res = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { if (m[i][j] == 1) { res++; infect(m, i, j, N, M); } } } return res; } public static void infect(int[][] m, int i, int j, int N, int M) { if (i < 0 || i >= N || j < 0 || j >= M || m[i][j] != 1) { return; } m[i][j] = 2; infect(m, i + 1, j, N, M); infect(m, i - 1, j, N, M); infect(m, i, j + 1, N, M); infect(m, i, j - 1, N, M); } public static void main(String[] args) { int[][] m1 = { { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 0, 1, 1, 1, 0 }, { 0, 1, 1, 1, 0, 0, 0, 1, 0 }, { 0, 1, 1, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }; System.out.println(countIslands(m1)); int[][] m2 = { { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 0, 0, 0, 1, 0 }, { 0, 1, 1, 0, 0, 0, 1, 1, 0 }, { 0, 0, 0, 0, 0, 1, 1, 0, 0 }, { 0, 0, 0, 0, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }; System.out.println(countIslands(m2)); } }
若是機器是多核CPU,能夠考慮把島分塊,每塊都統計出島的數量,而後把塊合併,合併的時候比較每一個相鄰塊的邊界,而後兩個點所屬的集合沒有相交過,島的數目減去1,而後記錄兩個點所屬的集合合併,以後在比較,若是兩個點的集合已經合併,繼續比較下一個節點。設計
並查集的定義:code
並查集(Union Find),又稱不相交集合(Disjiont Set),它應用於N個元素的集合求並與查 詢問題,在該應用場景中,咱們一般是在開始時讓每一個元素構成一個單元素的集合,然 後按必定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪 個集合中。雖然該問題並不複雜,但面對極大的數據量時,普通的數據結構每每沒法 解決,並查集就是解決該種問題最爲優秀的算法。blog
代碼實現:
typedef int NodeDot; class UnionFind { public: //集合個數 int count; UnionFind(vector<NodeDot>&nodes){ makeSets(nodes); } bool isSameSet(NodeDot a,NodeDot b){ return findHead(a) == findHead(b); } void uNion(NodeDot a,NodeDot b){ NodeDot aHead = findHead(a); NodeDot bHead = findHead(b); if(aHead!=bHead){ int aSetSize= sizeMap[aHead]; int bSetSize = sizeMap[bHead]; if (aSetSize <= bSetSize) { fatherMap[aHead]= bHead; sizeMap[bHead] = aSetSize + bSetSize; } else { fatherMap[bHead]= aHead; sizeMap[aHead] = aSetSize + bSetSize; } count--; } } private: // 查找節點的過程當中,把並查集打平 //讓全部的節點的父親都是 標誌節點 NodeDot findHead(NodeDot node){ NodeDot father = fatherMap[node]; if (father != node) { father = findHead(father); } fatherMap[node]=father; return father; } void makeSets(vector<NodeDot>nodes){ fatherMap.clear(); sizeMap.clear(); for (int i=0; i<nodes.size(); i++) { fatherMap[nodes[i]]=nodes[i]; sizeMap[nodes[i]]=1; } count=(int)nodes.size(); } //key爲 節點,value爲key所屬集合的 標誌節點 map<NodeDot,NodeDot> fatherMap; //key爲節點,value爲節點所屬的集合的所有節點個數 map<NodeDot,int>sizeMap; }; int findCircleNum2(vector<vector<int>>&M){ vector<int>vec; for (int i=0; i<M.size(); i++) { vec.push_back(i); } UnionFind set(vec); for (int i=0; i<M.size(); i++) { for (int j=i+1; j<M.size(); j++) { if(M[i][j]==1){//有交集,合併 set.uNion(i, j); } } } int iii = set.count; return set.count; }
leetCode的一道題能夠用並查集來求解:
朋友圈問題:
有N個同窗,他們之間有些是朋友,有些不是。"友誼"是能夠傳遞的,例如A與B是朋 友,B與C是朋友,
那麼A與C也是朋友;朋友圈就是完成"友誼"傳遞後的一組朋友。
給定N*N的矩陣表明同窗間是不是朋友,若是M[i][j] = 1表明第i個學生與第j個學生是朋 友,不然不是。求朋友圈的個數。
如圖:
int findCircleNum2(vector<vector<int>>&M){ vector<int>vec; for (int i=0; i<M.size(); i++) { vec.push_back(i); } UnionFind set(vec); for (int i=0; i<M.size(); i++) { for (int j=i+1; j<M.size(); j++) { if(M[i][j]==1){//有交集,合併 set.uNion(i, j); } } } return set.count; }