牛客網初級算法之五(哈希)

題目一:
設計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;
}
相關文章
相關標籤/搜索