思路:若是用单个哈希表,删除元素以后哈希表中会出现许多空隙,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; }