牛客网初级算法之五(哈希)

题目一:
设计RandomPool结构
【题目】 设计一种结构,在该结构中有以下三个功能:
insert(key):将某个key加入到该结构,作到不重复加入。
delete(key):将本来在结构中的某个key移除。
getRandom(): 等几率随机返回结构中的任何一个key。
【要求】 Insert、delete和getRandom方法的时间复杂度都是 O(1)

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