trie字典树可以用来查找单词或者搜索剪枝用。
Implement Trie (Prefix Tree) 实现一个 Trie,包含 insert
, search
, 和 startsWith
这三个方法。(模板必须记住;没有儿子建立儿子,有儿子走儿子;)
class Trie {
private class TrieNode {
public TrieNode[] children;
public boolean isword;
public TrieNode () {
this.children = new TrieNode[26];
this.isword = false;
}
}
/** Initialize your data structure here. */
private TrieNode root;
public Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
public void insert(String word) {
if(word == null || word.length() == 0) {
return;
}
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isword = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
TrieNode cur = searchPrefix(word);
return cur != null && cur.isword;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix) {
TrieNode cur = searchPrefix(prefix);
return cur != null;
}
private TrieNode searchPrefix(String prefix) {
if(prefix == null || prefix.length() == 0) {
return null;
}
TrieNode cur = root;
for(int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if(cur.children[c - 'a'] == null) {
return null;
}
cur = cur.children[c - 'a'];
}
return cur;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
Add and Search Word - Data structure design (遇见 ‘.’ 之后,for循环check每一个可能性;注意这题我写了两个坑:
1. index == word.length()的时候,返回的是cur.isword, 而不是直接返回true;
2. for循环的时候,一定要判断cur.children[i] != null, 也就是判断存入的单词,是否这条路径;搜索所有的路径,那就是DFS搜索,每一种情况都要check,只要有一种情况是true,那么就返回true;)
思路:这道题如果做过之前的那道
Implement Trie (Prefix Tree) 实现字典树(前缀树)的话就没有太大的难度了,因为这道题里面'.'可以代替任意字符,所以一旦有了'.',就需要查找之前存下的所有下一层的不是null的path;String match 的题,一般都是DFS 参数里面加入index,然后递归 subproblem求解;
class WordDictionary {
private class TrieNode {
public TrieNode[] children;
public boolean isword;
public String word;
public TrieNode() {
this.children = new TrieNode[26];
this.isword = false;
this.word = null;
}
}
private class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insert(String word) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isword = true;
cur.word = word;
}
public boolean search(String word) {
return ismatch(word, 0, root);
}
private boolean ismatch(String word, int index, TrieNode cur) {
if(index == word.length()) {
return cur.isword;
}
char c = word.charAt(index);
if(c == '.') {
for(int i = 0; i < 26; i++) {
if(cur.children[i] != null) { // 搜索存储的,下一层所有不是null的path;
if(ismatch(word, index + 1, cur.children[i])) {
return true;
}
}
}
return false;
} else {
if(cur.children[c - 'a'] == null) {
return false;
} else {
return ismatch(word, index + 1, cur.children[c - 'a']);
}
}
}
}
/** Initialize your data structure here. */
private Trie trie;
public WordDictionary() {
trie = new Trie();
}
/** Adds a word into the data structure. */
public void addWord(String word) {
trie.insert(word);
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
public boolean search(String word) {
return trie.search(word);
}
}
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary obj = new WordDictionary();
* obj.addWord(word);
* boolean param_2 = obj.search(word);
*/
Longest Word in Dictionary (思路1:这题标记为easy,有简单的方法,就是首先sort array,这样小的单词在前面,大的在后面,然后每个单词判断之前的substring(0, len - 1)是否在visited set里面,如果在,就表明之前的substring也是承接过来的,因为加入set的条件就是之前能够承接过来。思路2: 用trie做,可以做到O(n*L); 这题巧妙的是,build trie之后,可以bfs,层级的搜,要得到smallest lexicographical order.那么我最后搜集到的res,就是字典序最小的,因为是从后往前搜,然后最后一个答案就是最前面的;)
class Solution {
private class TrieNode {
public TrieNode[] children;
public boolean isword;
public String word;
public TrieNode () {
this.children = new TrieNode[26];
this.isword = false;
this.word = null;
}
}
private class Trie {
private TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insert(String word) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isword = true;
cur.word = word;
}
}
public String longestWord(String[] words) {
if(words == null) {
return null;
}
Trie trie = new Trie();
for(String word: words) {
trie.insert(word);
}
Queue<TrieNode> queue = new LinkedList<>();
queue.offer(trie.root);
String res = "";
while(!queue.isEmpty()) {
int size = queue.size();
TrieNode node = null;
for(int i = 0; i < size; i++) {
node = queue.poll();
// get first node as res; lex order is smallest;
if(i == 0 && node.isword) {
res = node.word;
}
// get next level;
for(int j = 0; j < 26; j++) {
if(node.children[j] != null && node.children[j].isword) {
queue.offer(node.children[j]);
}
}
}
}
return res;
}
}
====== Trie + DFS + BackTracking 搜索,这样的题,不少,出的也是很好的!=====
Word Search II (其实传递trietree进去,就已经相当于把所有的word全部传进去了,那么对于每个 x,y,那么只需要在这个点展开,搜索所有可能的string就可以了。参数传递不需要用word,只需要用trietree,因为是搜所有可能的word;
class Solution {
public class TrieNode {
public TrieNode[] children;
public boolean isword;
public String word;
public TrieNode () {
this.children = new TrieNode[26];
this.isword = false;
this.word = null;
}
}
public class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insertWord(String word) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isword = true;
cur.word = word;
}
}
public List<String> findWords(char[][] board, String[] words) {
Trie trie = new Trie();
for(String word: words) {
trie.insertWord(word);
}
int m = board.length;
int n = board[0].length;
HashSet<String> set = new HashSet<>();
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
boolean[][] visited = new boolean[m][n];
dfs(trie.root, board, i, j, visited, set);
}
}
return new ArrayList<String>(set);
}
int[][] dirs = {{0,1},{0,-1},{-1,0},{1,0}};
private void dfs(TrieNode cur, char[][] board, int x, int y, boolean[][] visited, HashSet<String> set) {
if(x < 0 || x >= board.length || y < 0 || y >= board[0].length || visited[x][y]) {
return;
}
char c = board[x][y];
if(cur.children[c - 'a'] == null) {
return;
}
cur = cur.children[c - 'a'];
if(cur.isword) {
set.add(cur.word);
}
visited[x][y] = true;
for(int[] dir: dirs) {
int nx = x + dir[0];
int ny = y + dir[1];
dfs(cur, board, nx, ny, visited, set);
}
visited[x][y] = false;
}
}
Concatenated Words 思路:先把单词insert into trie,好判断是否包含一个string,然后用trie 的root,来做DFS,每次找到一个单词,继续递归,index前进,一直到word.length() 同时count >=2代表是我想要的;注意:cur还是在走,但是每次是要从root开始搜单词;cur = root;
class Solution {
private class TrieNode {
public TrieNode[] children;
public boolean isword;
public String word;
public TrieNode () {
this.children = new TrieNode[26];
this.isword = false;
this.word = null;
}
}
private class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insert(String word) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
}
cur.isword = true;
cur.word = word;
}
}
public List<String> findAllConcatenatedWordsInADict(String[] words) {
List<String> res = new ArrayList<String>();
Trie trie = new Trie();
for(String word: words) {
trie.insert(word);
}
for(String word: words) {
if(dfs(word, trie.root, 0, 0)) {
res.add(word);
}
}
return res;
}
private boolean dfs(String word, TrieNode root, int index, int count) {
if(index == word.length()) {
return count >= 2;
}
TrieNode cur = root;
for(int i = index; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
return false;
}
cur = cur.children[c - 'a'];
// 如果当前是一个单词了,那么继续下一层的递归,如果是true,就返回true;
if(cur.isword) {
if(dfs(word, root, i + 1, count + 1)) {
return true;
}
}
}
return false;
}
}
Search Suggestions System (就是每个node存list of word,然后每次type word的时候,sort list,取走前面三个就是答案)
class Solution {
class TrieNode {
public TrieNode[] children;
public boolean isword;
public List<String> list;
public TrieNode () {
this.children = new TrieNode[26];
this.isword = false;
this.list = new ArrayList<>();
}
}
class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insertWord(String word) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
cur.list.add(word);
}
}
}
public List<List<String>> suggestedProducts(String[] products, String searchWord) {
List<List<String>> lists = new ArrayList<List<String>>();
Trie trie = new Trie();
for(String product: products) {
trie.insertWord(product);
}
TrieNode cur = trie.root;
for(int i = 0; i < searchWord.length(); i++) {
char c = searchWord.charAt(i);
List<String> list = new ArrayList<>();
if(cur != null && cur.children[c - 'a'] != null) {
cur = cur.children[c - 'a'];
List<String> temp = cur.list;
Collections.sort(temp);
for(int k = 0; k < 3 && k < temp.size(); k++) {
list.add(temp.get(k));
}
lists.add(list);
} else {
cur = null;
lists.add(list);
}
}
return lists;
}
}
Design Search Autocomplete System (跟上面的 Search Suggestions System很像,就是cur如果是null了,以后都是null了,这题不同的是,输入input的c,最后sb.toString()也要加入频率,所以trie node里面存string和频率,这题是用的hashmap来表示的children)
class AutocompleteSystem {
private class TrieNode {
public HashMap<Character, TrieNode> children; // 因为有空格;所以用hashmap,这样也节约空间;
public boolean isword;
public HashMap<String, Integer> freqMap;
public TrieNode () {
this.children = new HashMap<>();
this.isword = false;
this.freqMap = new HashMap<>();
}
}
private class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insert(String word, int time) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children.get(c) == null) {
cur.children.put(c, new TrieNode());
}
cur = cur.children.get(c);
// 一定是沿途的trieNode都要记录,而不是最后才记录;
cur.freqMap.put(word, cur.freqMap.getOrDefault(word, 0) + time);
}
cur.isword = true;
}
}
private class Node {
public String word;
public int freq;
public Node(String word, int freq) {
this.word = word;
this.freq = freq;
}
}
private Trie trie;
private TrieNode cur;
private StringBuilder sb;
public AutocompleteSystem(String[] sentences, int[] times) {
trie = new Trie();
for(int i = 0; i < sentences.length; i++) {
trie.insert(sentences[i], times[i]);
}
cur = trie.root;
sb = new StringBuilder();
}
public List<String> input(char c) {
List<String> res = new ArrayList<>();
if(c == '#') {
String lastword = sb.toString();
trie.insert(lastword, 1);
cur = trie.root;
sb = new StringBuilder(); // 记住,sb需要还原;
} else {
sb.append(c);
if(cur != null && cur.children.get(c) != null) {
cur = cur.children.get(c);
PriorityQueue<Node> pq = new PriorityQueue<Node>((a, b) -> (
a.freq != b.freq ? b.freq - a.freq : a.word.compareTo(b.word)));
for(String word: cur.freqMap.keySet()) {
pq.offer(new Node(word, cur.freqMap.get(word)));
}
int count = 0;
while(count < 3 && !pq.isEmpty()) {
res.add(pq.poll().word);
count++;
}
} else {
cur = null;
}
}
return res;
}
}
/**
* Your AutocompleteSystem object will be instantiated and called as such:
* AutocompleteSystem obj = new AutocompleteSystem(sentences, times);
* List<String> param_1 = obj.input(c);
*/
Word Squares (如何利用trie剪枝就是通过已经加入的单词list,构造出后面一定要出现的prefix是什么,也就是下一个string,开头必须是以已经输入的string的接下来的char组成的prefix开始,然后从trie中node的prefix list中选取candidate;)
class Solution {
private class TrieNode {
public TrieNode[] children;
public boolean isword;
public List<String> list;
public TrieNode () {
this.children = new TrieNode[26];
this.isword = false;
this.list = new ArrayList<String>();
}
}
private class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insert(String word) {
TrieNode cur = root;
for(int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if(cur.children[c - 'a'] == null) {
cur.children[c - 'a'] = new TrieNode();
}
cur = cur.children[c - 'a'];
cur.list.add(word);
}
cur.isword = true;
}
public List<String> getPrefix(String prefix) {
TrieNode cur = root;
List<String> res = new ArrayList<String>();
for(int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if(cur.children[c - 'a'] == null) {
return res;
}
cur = cur.children[c - 'a'];
}
res.addAll(cur.list);
return res;
}
}
public List<List<String>> wordSquares(String[] words) {
List<List<String>> lists = new ArrayList<List<String>>();
Trie trie = new Trie();
for(String word: words) {
trie.insert(word);
}
int n = words[0].length();
List<String> list = new ArrayList<String>();
for(String word: words) {
list.add(word); // 这里必须加入第一个单词,然后try所有的可能性;
dfs(words, lists, list, trie, n);
list.remove(list.size() - 1);
}
return lists;
}
private void dfs(String[] words, List<List<String>> lists,
List<String> list, Trie trie, int n) {
if(n == list.size()) {
lists.add(new ArrayList<String>(list));
return;
}
StringBuilder sb = new StringBuilder();
int index = list.size(); // 如果有两个单词,那么用前两个单词的第三个位子,拼凑成prefix;
for(int i = 0; i < list.size(); i++) {
sb.append(list.get(i).charAt(index));
}
// 从prefix的list中去选择;
for(String candidate: trie.getPrefix(sb.toString())) {
list.add(candidate);
dfs(words, lists, list, trie, n);
list.remove(list.size() - 1);
}
}
}
Palindrome Pairs (这题用hashmap,判断 reverseStr2 + str1 + str2, str1 + str2 + reverseStr1来做,注意要判断map.get(reverseStr1) != i) 防止自己是自己的palindrome,把自己加两遍的情况); 这题不是用trie做,用hashmap,像two sum做。
class Solution {
public List<List<Integer>> palindromePairs(String[] words) {
List<List<Integer>> lists = new ArrayList<List<Integer>>();
if(words == null || words.length == 0) {
return lists;
}
HashMap<String, Integer> hashmap = new HashMap<>();
for(int i = 0; i < words.length; i++) {
hashmap.put(words[i], i);
}
for(int i = 0; i < words.length; i++) {
String word = words[i];
for(int j = 0; j <= word.length(); j++) {
String str1 = word.substring(0, j);
String str2 = word.substring(j);
if(isPalindrome(str1)) {
String str2rvs = reverseString(str2);
// str1 is palindrome, str2rvs + str1 + str2 = str2rvs + word is palindrome.
// 题目要求是存[i, j] which words[i] + words[j] is palindrome;
// 那么就是 [str2rvs index, i]
if(hashmap.containsKey(str2rvs) && hashmap.get(str2rvs) != i) {
List<Integer> list = new ArrayList<Integer>();
list.add(hashmap.get(str2rvs));
list.add(i);
lists.add(list);
}
}
// 注意str2不能为空,因为前面已经有str2为空的情况,已经计算过了,否则这里会有重复结果;
// 另外,list add有顺序问题;
// str2 is palindrom, str1 + str2 + str1rvs = word + str1rvs is palindrome
// 题目要求是存[i, j] which words[i] + words[j] is palindrome;
// 那么就是[i, str1rvs index];
if(str2.length()!= 0 && isPalindrome(str2)) {
String str1rvs = reverseString(str1);
if(hashmap.containsKey(str1rvs) && hashmap.get(str1rvs) != i) {
List<Integer> list = new ArrayList<Integer>();
list.add(i);
list.add(hashmap.get(str1rvs));
lists.add(list);
}
}
}
}
return lists;
}
private boolean isPalindrome(String s) {
int start = 0; int end = s.length() - 1;
while(start <= end) {
if(s.charAt(start) != s.charAt(end)) {
return false;
}
start++;
end--;
}
return true;
}
private String reverseString(String s) {
char[] chars = s.toCharArray();
int i = 0; int j = chars.length - 1;
while(i <= j) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
i++;
j--;
}
return new String(chars);
}
}