字典树?我直接刷爆

csdn

字典树(Trie)

数据结构简介…

💻Java中的字典树结构:
java代码

💻Python中的字典树结构:
python


高频面试题

1.实现 Trie (前缀树)

🚀题目链接:LeetCode208.实现 Trie (前缀树)

题目:

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 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

🍬C++ AC代码:

//  字典树节点定义:
class TrieNode {
public:
    char val;
    bool isWord = false;
    TrieNode* children[26] = {nullptr};

    TrieNode() {}

    TrieNode(char c) {
        TrieNode* node = new TrieNode();
        node->val = c;
    }
};

class Trie {
private:
    TrieNode* root;

public:
    Trie() {
        root = new TrieNode();
        root->val = ' ';
    }
    
    void insert(string word) {
        TrieNode* ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word[i];
            if (ws->children[c - 'a'] == nullptr)
                ws->children[c - 'a'] = new TrieNode(c);
            ws = ws->children[c - 'a'];
        }
        ws->isWord = true;
    }
    
    bool search(string word) {
        TrieNode* ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word[i];
            if (ws->children[c - 'a'] == nullptr)
                return false;
            ws = ws->children[c - 'a'];
        }
        return ws->isWord;
    }
    
    bool startsWith(string prefix) {
        TrieNode* ws = root;
        for (int i = 0; i < prefix.length(); i++) {
            char c = prefix[i];
            if (ws->children[c - 'a'] == nullptr)
                return false;
            ws = ws->children[c - 'a'];
        }
        return true;
    }
};

Java AC代码:

class Trie {

    private TrieNode root;

    public Trie() {
        root = new TrieNode();
        root.val = ' ';
    }
    
    public void insert(String word) {
        TrieNode ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (ws.children[c - 'a'] == null)
                ws.children[c - 'a'] = new TrieNode(c);
            ws = ws.children[c - 'a'];
        }
        ws.isWord = true;
    }
    
    public boolean search(String word) {
        TrieNode ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (ws.children[c - 'a'] == null)
                return false;
            ws = ws.children[c - 'a'];
        }
        return ws.isWord;
    }
    
    public boolean startsWith(String prefix) {
        TrieNode ws = root;
        for (int i = 0; i < prefix.length(); i++) {
            char c = prefix.charAt(i);
            if (ws.children[c - 'a'] == null)
                return false;
            ws = ws.children[c - 'a'];
        }
        return true;
    }
}

//  字典树节点定义:
class TrieNode {

    public char val;
    public boolean isWord;
    public TrieNode[] children = new TrieNode[26];

    public TrieNode() {}

    TrieNode(char c) {
        TrieNode node = new TrieNode();
        node.val = c;
    }
}

🍦Python AC代码:

class Trie(object):

    def __init__(self):
        self.root = {}
        self.end_of_word = "#"

    def insert(self, word):
        node = self.root
        for char in word:
        	# 如果key存在则返回value,不存在则构建一个新的key-value再返回
            node = node.setdefault(char, {})
        node[self.end_of_word] = self.end_of_word


    def search(self, word):
        node = self.root
        for char in word:
            if char not in node:
                return False
            node = node[char]
        return self.end_of_word in node


    def startsWith(self, prefix):
        node = self.root
        for char in prefix:
            if char not in node:
                return False
            node = node[char]
        return True

Tips:

  • ⭐Python中可以使用字典dictionary来代替我们自己定义的字典树结构,key是字符charvalue是一个新的字典{}或者结束字符#

2. 单词搜索 II

🚀题目链接:LeetCode212. 单词搜索 II

题目:
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

示例 1:
1

输入:
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:
2

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

🍬C++ AC代码:

//  字典树节点定义:
struct Trie {
	Trie* child[26];
	string word = "";
	Trie() {
		for (int i = 0; i < 26; i++)
			child[i] = nullptr;
	}
};

class Solution {
public:
	// set保存结果用来去重
    set<string> temp_res;
    int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        vector<string> res;
        Trie* trie = new Trie();
        // 将单词加入字典树
        for (int i = 0; i < words.size(); i++) {
            Trie* cur = trie;
			for (int j = 0; j < words[i].size(); j++) {
				if (cur->child[words[i][j] - 'a'] == nullptr)
					cur->child[words[i][j] - 'a'] = new Trie();
				cur = cur->child[words[i][j] - 'a'];
			}
			cur->word = words[i];
        }
        int m = board.size(), n = board[0].size();
        // 对二维网格的每一个位置进行dfs搜索
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dfs(board, i, j, trie);
            }
        }
        // set转换成vector返回
        res.assign(temp_res.begin(), temp_res.end());
        return res;
    }

    void dfs(vector<vector<char>>& board, int x, int y, Trie* trie) {
        // 超出网格边界直接返回
        if (x < 0 || x >= board.size() || y < 0 || y >= board[0].size()) return;
        char c = board[x][y];
        // 检查是否访问过该位置和单词是否在字典树中存在
        if (c == '*' || trie->child[c-'a']==nullptr) return;
		trie = trie->child[c - 'a'];
		// 在字典树中存在当前dfs访问路径构成的单词
		if (trie->word != "") {
			temp_res.insert(trie->word);
			trie->word = "";
		}
		// 标记该位置已经访问
        board[x][y] = '*';
        // 递归访问上下左右四个方向
        for (int i = 0; i < 4; i++)
            dfs(board, x + dirs[i][0], y + dirs[i][1], trie);
        // 回溯
        board[x][y] = c;
    }
};

Tips:

  • ⭐C++代码调用上面一题已经写好的字典树中的方法提交上去会超时,这里将字典树的search()startWith()方法写在dfs中,避免每次dfs调用这两个方法造成不必要的时间开销。
  • dfs()函数向上下左右四个方向递归访问时,我们需要判断访问的位置之前是否已经访问过了,可以使用一个额外的bool类型的二维数组visited[][]标记走过的位置,更好的办法是向上面的代码这样,直接修改board数组,将访问过的位置置为"*"
  • ⭐经过测试使用额外数组标记的方式提交会超时,而下面的Java代码使用额外数组却能通过😂~

Java AC代码:

class Solution {

    // set用来存保存结果并去重
    Set<String> res = new HashSet<>();
    // 代表上下左右四个方向,dfs中会用到
    int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        // 将单词加入到字典树中
        for (String word : words)
            trie.insert(word);
        int m = board.length, n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        // 对二维网格的每一个位置进行dfs搜索
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dfs(board, visited, "", i, j, trie);
            }
        }
        return new ArrayList<String>(res);
    }

    public void dfs(char[][] board, boolean[][] visited, String str, int x, int y, Trie trie) {
        // 超出网格边界直接返回
        if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) return;
        // 访问到了之前访问过的位置,直接返回
        if (visited[x][y]) return;
        str += board[x][y];
        // 检查单词是否在字典树中存在
        if (!trie.startsWith(str)) return;
        if (trie.search(str)) res.add(str);
        visited[x][y] = true;
        // 递归访问上下左右四个方向
        for (int i = 0; i < 4; i++)
            dfs(board, visited, str, x + dirs[i][0], y + dirs[i][1], trie);
        // 回溯
        visited[x][y] = false;
    }
}

Tips:

  • ⭐Java代码要用到上面一题的字典树结构,这里只展示本题的核心代码,提交代码时记得复制一份字典树代码在本题后面😎。
  • ⭐Java代码超时的判定比较宽松,只要用了字典树就不会超时,不做额外的优化也可以通过。

🍦Python AC代码:

# 方向数组,用来dfs访问上下左右四个位置
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

# 字典树单词结束标志
END_OF_WORD = "#"

class Solution(object):
    def findWords(self, board, words):
        if not board or not board[0]: return []
        if not words: return []
		# set保存结果同时去重
        self.result = set()
		# 使用python中的dict字典创建字典树
        root = collections.defaultdict()
        # 将单词加入字典树
        for word in words:
            node = root
            for char in word:
                node = node.setdefault(char, collections.defaultdict())
            node[END_OF_WORD] = END_OF_WORD
        self.m, self.n = len(board), len(board[0])
		# 遍历网格中的每个位置,调用dfs搜索单词
        for i in xrange(self.m):
            for j in xrange(self.n):
                if board[i][j] in root:
                    self._dfs(board, i, j, "", root)
		# set转换成list返回
        return list(self.result)

    def _dfs(self, board, i, j, cur_word, cur_dict):
		# 获取当前字母在字典树中的位置
        cur_word += board[i][j]
        cur_dict = cur_dict[board[i][j]]
		# 当前字符是结束字符,说明找到了单词列表中的单词
        if END_OF_WORD in cur_dict:
            self.result.add(cur_word)
		# 标记当前位置已经访问过
        tmp, board[i][j] = board[i][j], '@'
        # 递归访问上下左右四个位置
        for k in xrange(4):
            x, y = i + dx[k], j + dy[k]
            # 判断访问的位置是否在网格内以及之前是否访问过
            if 0 <= x < self.m and 0 <= y < self.n and board[x][y] != '@' and board[x][y] in cur_dict:
                self._dfs(board, x, y, cur_word, cur_dict)
        # 回溯
        board[i][j] = tmp

Tips:

  • ⭐Python的代码执行时间比较长,我们需要标记已经访问过的位置,和C++代码的方式一样,直接修改原数组board即可,不用借助额外的标记数组,这里可以优化一下。

总结

┊回首向来萧瑟处,归去。也无风雨也无晴┊
-苏轼《定风波》-

footer
csdn

字典树(Trie)

数据结构简介…

💻Java中的字典树结构:
java代码

💻Python中的字典树结构:
python


高频面试题

1.实现 Trie (前缀树)

🚀题目链接:LeetCode208.实现 Trie (前缀树)

题目:

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 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

🍬C++ AC代码:

//  字典树节点定义:
class TrieNode {
public:
    char val;
    bool isWord = false;
    TrieNode* children[26] = {nullptr};

    TrieNode() {}

    TrieNode(char c) {
        TrieNode* node = new TrieNode();
        node->val = c;
    }
};

class Trie {
private:
    TrieNode* root;

public:
    Trie() {
        root = new TrieNode();
        root->val = ' ';
    }
    
    void insert(string word) {
        TrieNode* ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word[i];
            if (ws->children[c - 'a'] == nullptr)
                ws->children[c - 'a'] = new TrieNode(c);
            ws = ws->children[c - 'a'];
        }
        ws->isWord = true;
    }
    
    bool search(string word) {
        TrieNode* ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word[i];
            if (ws->children[c - 'a'] == nullptr)
                return false;
            ws = ws->children[c - 'a'];
        }
        return ws->isWord;
    }
    
    bool startsWith(string prefix) {
        TrieNode* ws = root;
        for (int i = 0; i < prefix.length(); i++) {
            char c = prefix[i];
            if (ws->children[c - 'a'] == nullptr)
                return false;
            ws = ws->children[c - 'a'];
        }
        return true;
    }
};

Java AC代码:

class Trie {

    private TrieNode root;

    public Trie() {
        root = new TrieNode();
        root.val = ' ';
    }
    
    public void insert(String word) {
        TrieNode ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (ws.children[c - 'a'] == null)
                ws.children[c - 'a'] = new TrieNode(c);
            ws = ws.children[c - 'a'];
        }
        ws.isWord = true;
    }
    
    public boolean search(String word) {
        TrieNode ws = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (ws.children[c - 'a'] == null)
                return false;
            ws = ws.children[c - 'a'];
        }
        return ws.isWord;
    }
    
    public boolean startsWith(String prefix) {
        TrieNode ws = root;
        for (int i = 0; i < prefix.length(); i++) {
            char c = prefix.charAt(i);
            if (ws.children[c - 'a'] == null)
                return false;
            ws = ws.children[c - 'a'];
        }
        return true;
    }
}

//  字典树节点定义:
class TrieNode {

    public char val;
    public boolean isWord;
    public TrieNode[] children = new TrieNode[26];

    public TrieNode() {}

    TrieNode(char c) {
        TrieNode node = new TrieNode();
        node.val = c;
    }
}

🍦Python AC代码:

class Trie(object):

    def __init__(self):
        self.root = {}
        self.end_of_word = "#"

    def insert(self, word):
        node = self.root
        for char in word:
        	# 如果key存在则返回value,不存在则构建一个新的key-value再返回
            node = node.setdefault(char, {})
        node[self.end_of_word] = self.end_of_word


    def search(self, word):
        node = self.root
        for char in word:
            if char not in node:
                return False
            node = node[char]
        return self.end_of_word in node


    def startsWith(self, prefix):
        node = self.root
        for char in prefix:
            if char not in node:
                return False
            node = node[char]
        return True

Tips:

  • ⭐Python中可以使用字典dictionary来代替我们自己定义的字典树结构,key是字符charvalue是一个新的字典{}或者结束字符#

2. 单词搜索 II

🚀题目链接:LeetCode212. 单词搜索 II

题目:
给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

示例 1:
1

输入:
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:
2

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

🍬C++ AC代码:

//  字典树节点定义:
struct Trie {
	Trie* child[26];
	string word = "";
	Trie() {
		for (int i = 0; i < 26; i++)
			child[i] = nullptr;
	}
};

class Solution {
public:
	// set保存结果用来去重
    set<string> temp_res;
    int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        vector<string> res;
        Trie* trie = new Trie();
        // 将单词加入字典树
        for (int i = 0; i < words.size(); i++) {
            Trie* cur = trie;
			for (int j = 0; j < words[i].size(); j++) {
				if (cur->child[words[i][j] - 'a'] == nullptr)
					cur->child[words[i][j] - 'a'] = new Trie();
				cur = cur->child[words[i][j] - 'a'];
			}
			cur->word = words[i];
        }
        int m = board.size(), n = board[0].size();
        // 对二维网格的每一个位置进行dfs搜索
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dfs(board, i, j, trie);
            }
        }
        // set转换成vector返回
        res.assign(temp_res.begin(), temp_res.end());
        return res;
    }

    void dfs(vector<vector<char>>& board, int x, int y, Trie* trie) {
        // 超出网格边界直接返回
        if (x < 0 || x >= board.size() || y < 0 || y >= board[0].size()) return;
        char c = board[x][y];
        // 检查是否访问过该位置和单词是否在字典树中存在
        if (c == '*' || trie->child[c-'a']==nullptr) return;
		trie = trie->child[c - 'a'];
		// 在字典树中存在当前dfs访问路径构成的单词
		if (trie->word != "") {
			temp_res.insert(trie->word);
			trie->word = "";
		}
		// 标记该位置已经访问
        board[x][y] = '*';
        // 递归访问上下左右四个方向
        for (int i = 0; i < 4; i++)
            dfs(board, x + dirs[i][0], y + dirs[i][1], trie);
        // 回溯
        board[x][y] = c;
    }
};

Tips:

  • ⭐C++代码调用上面一题已经写好的字典树中的方法提交上去会超时,这里将字典树的search()startWith()方法写在dfs中,避免每次dfs调用这两个方法造成不必要的时间开销。
  • dfs()函数向上下左右四个方向递归访问时,我们需要判断访问的位置之前是否已经访问过了,可以使用一个额外的bool类型的二维数组visited[][]标记走过的位置,更好的办法是向上面的代码这样,直接修改board数组,将访问过的位置置为"*"
  • ⭐经过测试使用额外数组标记的方式提交会超时,而下面的Java代码使用额外数组却能通过😂~

Java AC代码:

class Solution {

    // set用来存保存结果并去重
    Set<String> res = new HashSet<>();
    // 代表上下左右四个方向,dfs中会用到
    int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        // 将单词加入到字典树中
        for (String word : words)
            trie.insert(word);
        int m = board.length, n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        // 对二维网格的每一个位置进行dfs搜索
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dfs(board, visited, "", i, j, trie);
            }
        }
        return new ArrayList<String>(res);
    }

    public void dfs(char[][] board, boolean[][] visited, String str, int x, int y, Trie trie) {
        // 超出网格边界直接返回
        if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) return;
        // 访问到了之前访问过的位置,直接返回
        if (visited[x][y]) return;
        str += board[x][y];
        // 检查单词是否在字典树中存在
        if (!trie.startsWith(str)) return;
        if (trie.search(str)) res.add(str);
        visited[x][y] = true;
        // 递归访问上下左右四个方向
        for (int i = 0; i < 4; i++)
            dfs(board, visited, str, x + dirs[i][0], y + dirs[i][1], trie);
        // 回溯
        visited[x][y] = false;
    }
}

Tips:

  • ⭐Java代码要用到上面一题的字典树结构,这里只展示本题的核心代码,提交代码时记得复制一份字典树代码在本题后面😎。
  • ⭐Java代码超时的判定比较宽松,只要用了字典树就不会超时,不做额外的优化也可以通过。

🍦Python AC代码:

# 方向数组,用来dfs访问上下左右四个位置
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

# 字典树单词结束标志
END_OF_WORD = "#"

class Solution(object):
    def findWords(self, board, words):
        if not board or not board[0]: return []
        if not words: return []
		# set保存结果同时去重
        self.result = set()
		# 使用python中的dict字典创建字典树
        root = collections.defaultdict()
        # 将单词加入字典树
        for word in words:
            node = root
            for char in word:
                node = node.setdefault(char, collections.defaultdict())
            node[END_OF_WORD] = END_OF_WORD
        self.m, self.n = len(board), len(board[0])
		# 遍历网格中的每个位置,调用dfs搜索单词
        for i in xrange(self.m):
            for j in xrange(self.n):
                if board[i][j] in root:
                    self._dfs(board, i, j, "", root)
		# set转换成list返回
        return list(self.result)

    def _dfs(self, board, i, j, cur_word, cur_dict):
		# 获取当前字母在字典树中的位置
        cur_word += board[i][j]
        cur_dict = cur_dict[board[i][j]]
		# 当前字符是结束字符,说明找到了单词列表中的单词
        if END_OF_WORD in cur_dict:
            self.result.add(cur_word)
		# 标记当前位置已经访问过
        tmp, board[i][j] = board[i][j], '@'
        # 递归访问上下左右四个位置
        for k in xrange(4):
            x, y = i + dx[k], j + dy[k]
            # 判断访问的位置是否在网格内以及之前是否访问过
            if 0 <= x < self.m and 0 <= y < self.n and board[x][y] != '@' and board[x][y] in cur_dict:
                self._dfs(board, x, y, cur_word, cur_dict)
        # 回溯
        board[i][j] = tmp

Tips:

  • ⭐Python的代码执行时间比较长,我们需要标记已经访问过的位置,和C++代码的方式一样,直接修改原数组board即可,不用借助额外的标记数组,这里可以优化一下。

总结

┊回首向来萧瑟处,归去。也无风雨也无晴┊
-苏轼《定风波》-

footer

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值