劍指 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 。

  • 終止條件:

  1. 返回 false : (1) 行或列索引越界 或 (2) 當前矩陣元素與目標字元不同 或 (3) 當前矩陣元素已訪問過 ( (3) 可合併至 (2) ) 。
  2. 返回 true : k = len(word) - 1 ,即字元串 word 已全部匹配。
  • 遞推工作:
  1. 標記當前矩陣元素: 將 board[i][j] 修改為 空字元 '' ,代表此元素已訪問過,防止之後搜索時重複訪問。
  2. 搜索下一單元格: 朝當前元素的 上、下、左、右 四個方向開啟下層遞歸,使用 或 連接 (代表只需找到一條可行路徑就直接返回,不再做後續 DFS ),並記錄結果至 res 。
  3. 還原當前矩陣元素: 將 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; // 返回結果
    }
}

參考鏈接:

//leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/solution/mian-shi-ti-12-ju-zhen-zhong-de-lu-jing-shen-du-yo/