LeetCode 79 Word Search
方法:DFS
时间复杂度:O(mn4l)
空间复杂度:O(mn + l)
想法:其实这题就是常规的DFS,没法写记忆化。但是这个DFS的递归出口我觉得还是有一定的可学习总结之处的。首先,基本思路就是反正枚举每个格子,然后试一下看从这个地方开始DFS能不能给找出一条word路径来。用一个visited数组来放置同一个地方的重复访问。到这里都还是比较基础的,主要是有两点。
- 不能用记忆化。因为当从每个格子为起点开始搜路径的时候,比方说搜到一半,word里面还剩一部分要找,这时候确认可以组成word前半部分的那些格子是不能再重复访问的。那比方说之后你以另一个格子为起点搜,搜到了某个位置,word里面也剩一部分,哪怕位置一样、word剩的长度一样,也不能直接记下来返回,因为这两次搜索当中搜到这个地方的状态是不一样的,第一种情况跟第二种情况是从不同的地方开始搜,到这个点的时候不能够再去访问的位置是不一样的。
- 对于DFS的递归出口的考虑。首先,我们用一个变量index记录在word里面我们找到了哪个下标了,那么DFS只会在搜索到的当前位置的格子的字符等于word对应的index这地方的字符的时候,我们才会往下继续搜索,因此当真正在递归里调用自己的时候,他其实已经是在判定完了这俩字符相等不相等之后了。假设说index已经指向了word的最后一个字符,我们知道,如果这个时候,board[x][y]==word.charAt(index),那其实就已经说明了我们搜完了,搜到了一个合法的结果,所以递归可以在index == word.length() - 1的时候结束。如果是按照这样的写法,判定这个位置首先合不合法就应该在index的判断前面,如果不合法直接返回false。这样的写法写出来的递归出口会是:
还有另外一种写法,经常刷LeetCode的老司机应该会觉得按照index去进行DFS的时候,递归出口很多时候是index==word.length(),这个时候返回true,而不是使用index==word.length()-1。这种写法也可以写。相当于说,匹配到word的最后一个字符之后,我们还会向下再递归一层,但我们会保证这一层全部返回true。这种写法写出来会是:if (!isValid(x, y) || visited[x][y] || board[x][y] != word.charAt(index)) { return false; } if (index == word.length() - 1) { return true; }以上两种都是可以的,但是这道题要避免一边在dfs的开头写递归出口,一边又在往下递归的时候施加限定。比方说这样写:if (index == word.length()) { return true; } if (!isValid(x, y) || visited[x][y] || board[x][y] != word.charAt(index)) { return false; }假如这样写的话,比方说样例[["a"]] "a"。先开始index=0,然后这俩字符相等,开始搜,这时候会发现没有下一个位置,因为没有下一个位置,这里就不会在for循环里返回true,会在最后返回false,但其实这个情况应当返回true。if (index == word.length()) { return true; } if (board[x][y] != word.charAt(index)) { return false; } visited[x][y] = true; for (int i = 0; i < 4; i++) { int nx = x + dx[i]; int ny = y + dy[i]; if (isValid(nx, ny) && !visited[nx][ny]) { if (dfs(board, nx, ny, index + 1, word, visited)) { return true; } } } visited[x][y] = false;
代码:
class Solution {
private int m, n;
private int[] dx = new int[] {-1, 0, 1, 0};
private int[] dy = new int[] {0, -1, 0, 1};
public boolean exist(char[][] board, String word) {
m = board.length;
n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (dfs(board, i, j, 0, word, visited)) {
return true;
}
}
}
return false;
}
private boolean dfs(char[][] board, int x, int y, int index, String word, boolean[][] visited) {
if (!isValid(x, y) || visited[x][y] || board[x][y] != word.charAt(index)) {
return false;
}
if (index == word.length() - 1) {
return true;
}
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (dfs(board, nx, ny, index + 1, word, visited)) {
return true;
}
}
visited[x][y] = false;
return false;
}
private boolean isValid(int x, int y) {
return x >= 0 && x < m && y >= 0 && y < n;
}
}
LeetCode 212 Word Search II
方法:Trie+DFS
时间复杂度:O(mn3ll),分析参考www.acwing.com/solution/Le…
空间复杂度:字典树的大小,与字符串集合的前缀情况有关
想法:比较典型的使用Trie进行DFS剪枝的题目。剪枝的思想就是,上去先开一个Trie,把这道题words里面所有的单词全存进去,然后这样的话,比方说我在DFS的时候搜到了一个位置,它这个地方是"a",然后它的四周分别是"b","c""d","e",然后先看一下在Trie这个对应的节点上,到底有没有个child是b或c或d或e,比方说只有ae,那么b、c、d就统统都不要去了,只往e这个地方往下递归。
代码:
class TrieNode {
public String word;
public TrieNode[] children;
public TrieNode() {
this.word = null;
this.children = new TrieNode[26];
}
}
class Trie {
public TrieNode root;
public Trie() {
this.root = new TrieNode();
}
public void insert(String s) {
TrieNode p = root;
for (int i = 0; i < s.length(); i++) {
int index = s.charAt(i) - 'a';
if (p.children[index] == null) {
p.children[index] = new TrieNode();
}
p = p.children[index];
}
p.word = s;
}
}
class Solution {
private int m, n;
private int[] dx = {-1, 0, 1, 0};
private int[] dy = {0, -1, 0, 1};
public List<String> findWords(char[][] board, String[] words) {
m = board.length;
n = board[0].length;
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
List<String> res = new ArrayList<>();
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dfs(board, i, j, trie.root, visited, res);
}
}
return res;
}
private void dfs(char[][] board, int x, int y, TrieNode node, boolean[][] visited, List<String> res) {
int index = board[x][y] - 'a';
if (node.children[index] == null) {
return;
}
TrieNode child = node.children[index];
if (child.word != null) {
res.add(child.word);
child.word = null;
}
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (isValid(nx, ny) && !visited[nx][ny]) {
dfs(board, nx, ny, child, visited, res);
}
}
visited[x][y] = false;
}
private boolean isValid(int x, int y) {
return x >= 0 && x < m && y >= 0 && y < n;
}
}