算法12-字典樹和并查集

《算法文章匯總》

字典樹,即Trie樹,又稱單詞查找樹或鍵樹,是一種樹形結(jié)構(gòu)。典型應(yīng)用是用于統(tǒng)計(jì)和排序大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計(jì)。

它的優(yōu)點(diǎn)是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。

基本性質(zhì)

1.節(jié)點(diǎn)本身不存完整單詞
2.從根節(jié)點(diǎn)到某一節(jié)點(diǎn),路徑上經(jīng)過的字符連接起來,為該節(jié)點(diǎn)對應(yīng)的字符串;
3.每個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn)路徑代表的字符都不相同

trie核心思想是空間換時(shí)間

利用字符串的公共前綴來降低查詢時(shí)間的開銷以達(dá)到提高效率的目的

節(jié)點(diǎn)的內(nèi)部實(shí)現(xiàn)

圖片.png

實(shí)現(xiàn)Trie

Trie(發(fā)音類似 "try")或者說 前綴樹 是一種樹形數(shù)據(jù)結(jié)構(gòu),用于高效地存儲和檢索字符串?dāng)?shù)據(jù)集中的鍵。這一數(shù)據(jù)結(jié)構(gòu)有相當(dāng)多的應(yīng)用情景,例如自動補(bǔ)完和拼寫檢查。
請你實(shí)現(xiàn) Trie 類:
Trie() 初始化前綴樹對象。
void insert(String word) 向前綴樹中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前綴樹中,返回 true(即,在檢索之前已經(jīng)插入);否則,返回 false 。
boolean startsWith(String prefix) 如果之前已經(jīng)插入的字符串 word 的前綴之一為 prefix ,返回 true ;否則,返回 false 。
示例:
輸入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
輸出
[null, null, true, false, true, null, true]

解釋
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 True
trie.search("app");     // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app");     // 返回 True

向 Trie 樹中插入鍵
我們通過搜索 Trie 樹來插入一個(gè)鍵。我們從根開始搜索它對應(yīng)于第一個(gè)鍵字符的鏈接。有兩種情況:

鏈接存在。沿著鏈接移動到樹的下一個(gè)子層。算法繼續(xù)搜索下一個(gè)鍵字符。
鏈接不存在。創(chuàng)建一個(gè)新的節(jié)點(diǎn),并將它與父節(jié)點(diǎn)的鏈接相連,該鏈接與當(dāng)前的鍵字符相匹配。
重復(fù)以上步驟,直到到達(dá)鍵的最后一個(gè)字符,然后將當(dāng)前節(jié)點(diǎn)標(biāo)記為結(jié)束節(jié)點(diǎn),算法完成。

class Trie {
    class TrieNode {
        //結(jié)點(diǎn)數(shù)
        private TrieNode[] links;
        private final int R = 26;
        private boolean isEnd;
        public TrieNode(){
            links = new TrieNode[R];
        }

        public boolean containsKey(char ch){
            return links[ch - 'a'] != null;
        }

        public TrieNode get(char ch){
            return links[ch - 'a'];
        }

        public void put(char ch, TrieNode node){
            links[ch - 'a'] = node;
        }

        public void setIsEnd(){
            isEnd = true;
        }

        public boolean isEnd(){
            return isEnd;
        }
    }
     private TrieNode root;
     public Trie(){
         root = new TrieNode();
     }

     //Inserts a word into the trie
    public void  insert(String word){
       TrieNode node = root;
       for (int i = 0;i<word.length();i++){
           char currentChar = word.charAt(i);
           if (!node.containsKey(currentChar)){
               node.put(currentChar,new TrieNode());
           }
           node = node.get(currentChar);
       }
       node.setIsEnd();
    }

    //search a prefix or whole word in trie and returns the node when search ends
    private TrieNode searchPrefix(String word){
        TrieNode node = root;
        for (int i = 0;i<word.length();i++){
            char curLetter = word.charAt(i);
            if (node.containsKey(curLetter)){
                node = node.get(curLetter);
            }else{
                return null;
            }
        }
        return node;
    }

    //returns if the word is in the trie
    public boolean search(String word){
        TrieNode node = searchPrefix(word);
        return node != null && node.isEnd();
    }

    public boolean startsWith(String prefix){
        TrieNode node = searchPrefix(prefix);
        return node != null;
    }
}

單詞搜索II

給定一個(gè) m x n 二維字符網(wǎng)格 board 和一個(gè)單詞(字符串)列表 words,找出所有同時(shí)在二維網(wǎng)格和字典中出現(xiàn)的單詞。
單詞必須按照字母順序,通過 相鄰的單元格 內(nèi)的字母構(gòu)成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個(gè)單元格內(nèi)的字母在一個(gè)單詞中不允許被重復(fù)使用。
示例 1:

圖片.png

輸入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
輸出:["eat","oath"]
示例 2:

圖片.png

輸入:board = [["a","b"],["c","d"]], words = ["abcb"]
輸出:[]

并查集

圖片.png

Quick Union Ranks
Path Halving Path Spliting

/**
 * 項(xiàng)目名稱:Arithmetic
 * 類 名 稱:UnionFind_QU_Rank
 * 類 描 述:TODO
 * 創(chuàng)建時(shí)間:2021/4/27 7:53 PM
 * 創(chuàng) 建 人:cloud
 */
public class UnionFind_QU_Rank extends UnionFind_QU {
    private int[] ranks;
    public UnionFind_QU_Rank(int capacity) {
        super(capacity);
        ranks = new int[capacity];
        //以i為根節(jié)點(diǎn)的這棵樹的高度
        for (int i=0;i<ranks.length;i++){
            ranks[i] = 1;
        }
    }
    public void union(int v1,int v2){
        int p1 = parents[v1];
        int p2 = parents[v2];
        if (p1 == p2) return;
        if (ranks[p1] < ranks[p2]){
            parents[p1] = p2;
        }else if(ranks[p1] > ranks[p2]){
            parents[p2] = p1;
        }else{
            parents[p1] = p2;
            ranks[p2] ++;
        }
    }
}

省份數(shù)量

有 n 個(gè)城市,其中一些彼此相連,另一些沒有相連。如果城市 a 與城市 b 直接相連,且城市 b 與城市 c 直接相連,那么城市 a 與城市 c 間接相連。
省份 是一組直接或間接相連的城市,組內(nèi)不含其他沒有相連的城市。
給你一個(gè) n x n 的矩陣 isConnected ,其中 isConnected[i][j] = 1 表示第 i 個(gè)城市和第 j 個(gè)城市直接相連,而 isConnected[i][j] = 0 表示二者不直接相連。
返回矩陣中 省份 的數(shù)量。


image.png

輸入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
輸出:2

import org.junit.Test;

/**
 * 項(xiàng)目名稱:Arithmetic
 * 類 名 稱:UnionFind0
 * 類 描 述:TODO
 * 創(chuàng)建時(shí)間:2021/4/27 10:50 PM
 * 創(chuàng) 建 人:cloud
 */
public class Solution0{
    public class UnionFind0 {
        public int[] parents;
        public int sizes;//集合數(shù)量

        public UnionFind0(int n) {
            parents = new int[n];
            for (int i = 0; i < n; i++) {
                parents[i] = i;
            }
            sizes = n;
        }

        //快速查找根節(jié)點(diǎn):路徑壓縮,使路徑上所有節(jié)點(diǎn)都指向根節(jié)點(diǎn),從而降低樹的高度
        public int find(int v) {
            rangeCheck(v);
            if (parents[v] != v) {
                parents[v] = find(parents[v]);
            }
            return parents[v];
        }

        //是不是相同的集合,聯(lián)通的點(diǎn)
        public boolean isSame(int v1, int v2) {
            return find(v1) == find(v2);
        }

        //合并v1,v2
        public void union(int v1, int v2) {
            int p1 = find(v1);
            int p2 = find(v2);
            if (p1 == p2) return;
            if (p1 != p2) {
                parents[p1] = p2;
                sizes--;
            }
        }

        //檢查邊界
        public void rangeCheck(int v) {
            if (v < 0 || v > parents.length) {
                throw new IllegalArgumentException("v is out of bounds");
            }
        }
    }
    public int findCircleNum(int[][] isConnected) {
        //初始化并查集
        UnionFind0 unionFind0 = new UnionFind0(isConnected.length);
        for (int i = 0; i < isConnected.length; i++) {
            for (int j = i + 1; j < isConnected.length; j++) {
                if (isConnected[i][j] == 1) {
                    unionFind0.union(i, j);
                }
            }
        }
        return unionFind0.sizes;
    }
    @Test
    public void testCombineFindSet() {
//        int[][] isConnected = {{1,1,0},{1,1,0},{0,0,1}};
//        System.out.println(findCircleNum(isConnected));
//        int[][] isConnected1 = {{1,0,0},{0,1,0},{0,0,1}};
//        System.out.println(findCircleNum(isConnected1));
        int[][] isConnected2 = {{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1},{1,1,1,1,1}};
        System.out.println(findCircleNum(isConnected2));
    }
}

島嶼數(shù)量

public class UnionFind1{
        //根節(jié)點(diǎn)數(shù)組
        public int[] parents;
        //集合數(shù)量
        public int sizes;
        UnionFind1(int capacity){
            parents = new int[capacity];
            for (int i = 0;i<capacity;i++){
                parents[i] = i;
            }
            sizes = capacity;
        }
        //檢查邊界
        public void rangeCheck(int v){
            if (v < 0 || v > parents.length)
                throw new IllegalArgumentException("v is out of bounds");
        }
        //路徑減半:將一條線上面的所有節(jié)點(diǎn)都指向祖父節(jié)點(diǎn),壓縮了樹的高度
        public int find(int v){
            rangeCheck(v);
            while(v !=parents[v]){
                parents[v] = parents[parents[v]];
                v = parents[v];
            }
            return v;
        }

        //快速合并兩個(gè)集合,集合的根節(jié)點(diǎn)中一方指向另外一方
        public void union(int v1,int v2){
            int p1 = find(v1);
            int p2 = find(v2);
            //同一個(gè)集合,不需要合并
            if (p1 == p2) return;
            if (p1 != p2){
                parents[p1] = p2;
                sizes--;
            }
        }
    }

    //島嶼數(shù)量,集合數(shù)量
    public int numIslands(char[][] grid) {
        //如果是一行一列
        if (grid.length == 0 || grid == null) return 0;
        int row = grid.length;
        int col = grid[0].length;
        int water = 0;//水域(0)個(gè)數(shù)

        UnionFind1 unionFind1  = new UnionFind1(row * col);
        for (int i = 0;i<row;i++){
            for (int j = 0 ;j<col;j++){
                if (grid[i][j] == '0') water++;
                else{
                    //"1"的島嶼 注意:方向數(shù)組d是上下左右搜索的常用手法
                    int[][] directions = {{0,1},{0,-1},{1,0},{-1,0}};
                    for (int[] dir:directions){
                        int x = i+dir[0];
                        int y = j+dir[1];
                        //判斷是否越界
                        if (x>=0 && y>=0 && x<row && y<col && grid[x][y] == '1'){
                            //合并
                            unionFind1.union(i*col+j,x*col+y);
                        }
                    }
                }
            }
        }
        return unionFind1.sizes - water;
    }

被圍繞的區(qū)域

給你一個(gè) m x n 的矩陣 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 圍繞的區(qū)域,并將這些區(qū)域里所有的 'O' 用 'X' 填充。


圖片.png

輸入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
輸出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解釋:被圍繞的區(qū)間不會存在于邊界上,換句話說,任何邊界上的 'O' 都不會被填充為 'X'。 任何不在邊界上,或不與邊界上的 'O' 相連的 'O' 最終都會被填充為 'X'。如果兩個(gè)元素在水平或垂直方向相鄰,則稱它們是“相連”的。
示例 2:
輸入:board = [["X"]]
輸出:[["X"]]
思路:我們的思路是把所有邊界上的 OO 看做一個(gè)連通區(qū)域。遇到 OO 就執(zhí)行并查集合并操作,這樣所有的 OO 就會被分成兩類
和邊界上的 OO 在一個(gè)連通區(qū)域內(nèi)的。這些 OO 我們保留。
不和邊界上的 OO 在一個(gè)連通區(qū)域內(nèi)的。這些 OO 就是被包圍的,替換。
由于并查集我們一般用一維數(shù)組來記錄,方便查找 parants,所以我們將二維坐標(biāo)用 node 函數(shù)轉(zhuǎn)化為一維坐標(biāo)。

class Solution {
public class UnionFind2{
        //根節(jié)點(diǎn)數(shù)組
        public int[] parents;
        UnionFind2(int capacity){
            parents = new int[capacity];
            for (int i=0;i<capacity;i++){
                parents[i] = i;
            }
        }

        public int find(int v){
            checkRange(v);
            //路徑減半
            while (parents[v] != v){
                parents[v] = parents[parents[v]];
                v = parents[v];
            }
            return v;
        }

        public void union(int v1,int v2){
            int p1 = find(v1);
            int p2 = find(v2);
            if (p1 == p2)return;
            parents[p1] = p2;
        }

        public boolean isConnected(int v1,int v2){
            return find(v1) == find(v2);
        }

        public void checkRange(int v){
            if (v < 0 || v > parents.length)
                throw new IllegalArgumentException("v is out of bound");
        }
    }
    public int row;
    public int column;
    public void solve(char[][] board) {
        row = board.length;
        column = board[0].length;
        if (row == 0 || column == 0) return;
        UnionFind2 unionFind2 = new UnionFind2(row * column + 1);
        //標(biāo)記邊界連通為一個(gè)區(qū)域
        int dummy = row * column;
        for (int i = 0;i<row;i++)
            for (int j = 0;j<column;j++){
                if (board[i][j] == 'O'){
                    //處于邊界的位置,則連通為一個(gè)
                    if (i == 0 || i == row - 1 || j == 0 || j == column-1)
                    unionFind2.union(node(i,j),dummy);
                    //不在邊界位置的0,上下左右合并
                    else{
                        //上下左右合并聯(lián)通
                       if (i > 0 && board[i-1][j] == 'O'){
                          unionFind2.union(node(i,j),node(i-1,j));
                       }
                       if (i < row - 1 && board[i+1][j] == 'O'){
                           unionFind2.union(node(i,j),node(i+1,j));
                       }
                       if (j > 0 && board[i][j -1] == 'O'){
                           unionFind2.union(node(i,j),node(i,j-1));
                       }
                       if (j < column - 1 && board[i][j+1] == 'O'){
                           unionFind2.union(node(i,j),node(i,j+1));
                       }
                    }
                }
            }

        for (int i = 0;i<row;i++)
            for (int j=0;j<column;j++){
                if (unionFind2.isConnected(node(i,j),dummy)){
                    board[i][j] = 'O';
                }else{
                    board[i][j] = 'X';
                }
            }
    }

    public int node(int i,int j){
        return i*column +j;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容