劍指 Offer 12. 矩陣中的路徑
劍指 Offer 12. 矩陣中的路徑
給定一個 m x n 二維字元網格 board 和一個字元串單詞 word 。如果 word 存在於網格中,返回 true ;否則,返回 false 。
單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中「相鄰」單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母不允許被重複使用。
例如,在下面的 3×4 的矩陣中包含單詞 "ABCCED"(單詞中的字母已標出)。

示例 1:
輸入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
輸出:true
示例 2:
輸入:board = [["a","b"],["c","d"]], word = "abcd"
輸出:false
提示:
- 1 <= board.length <= 200
- 1 <= board[i].length <= 200
- board 和 word 僅由大小寫英文字母組成
解題思路:
這道題說實話,看出來了DFS,但還是不怎麼熟練,直到看了k神的題解才恍然大悟,題解如下:
DFS解析:
-
遞歸參數: 當前元素在矩陣 board 中的行列索引 i 和 j ,當前目標字元在 word 中的索引 k 。
-
終止條件:
- 返回 false : (1) 行或列索引越界 或 (2) 當前矩陣元素與目標字元不同 或 (3) 當前矩陣元素已訪問過 ( (3) 可合併至 (2) ) 。
- 返回 true : k = len(word) - 1 ,即字元串 word 已全部匹配。
- 遞推工作:
- 標記當前矩陣元素: 將 board[i][j] 修改為 空字元 '' ,代表此元素已訪問過,防止之後搜索時重複訪問。
- 搜索下一單元格: 朝當前元素的 上、下、左、右 四個方向開啟下層遞歸,使用 或 連接 (代表只需找到一條可行路徑就直接返回,不再做後續 DFS ),並記錄結果至 res 。
- 還原當前矩陣元素: 將 board[i][j] 元素還原至初始值,即 word[k] 。
- 返回值: 返回布爾量 res ,代表是否搜索到目標字元串。
使用空字元(Python: '' , Java/C++: '\0' )做標記是為了防止標記字元與矩陣原有字元重複。當存在重複時,此演算法會將矩陣原有字元認作標記字元,從而出現錯誤。
程式碼如下:
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
if(k == word.length - 1) return true;
board[i][j] = '\0';
boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = word[k];
return res;
}
}
注釋版本如下:
class Solution {
public boolean exist(char[][] board, String word) {
int m = board.length;
int n = board[0].length;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
// 搜索開始的位置可以是網格 board 中的任一個位置
// z只要有一條路走通了,則可以直接返回結果
if(backtrack(board, i, j, word, 0)) return true;
}
}
return false;
}
// board:網格, i:當前行, j:當前列, word:待匹配字元串, k:當前匹配到字元串word的位置
public boolean backtrack(char[][] board, int i, int j, String word, int k){
// 1. 下標越界,2. 當前位置與word中字元不匹配,則可以直接返回(剪枝)
if( i < 0 || j < 0 || i >= board.length || j >= board[i].length || board[i][j] != word.charAt(k)) return false;
if(k == word.length() - 1) return true; // 說明word中的字元全部被匹配
board[i][j] = '\0'; // 使用board數組充當了訪問標記數組,節省了空間(非常妙!)
// 做出選擇:可以向 上,下,左,右移動,只要有一條路可以走通即可, 所以使用 ||
boolean res = backtrack(board, i, j + 1, word, k+1) || backtrack(board, i + 1, j, word, k+1) ||
backtrack(board, i-1, j, word,k+1) || backtrack(board, i, j-1, word,k+1);
board[i][j] = word.charAt(k); // 撤銷選擇, 還原board數組
return res; // 返回結果
}
}
參考鏈接:

