1. 程式人生 > >劍指Offer-67-矩陣中的路徑

劍指Offer-67-矩陣中的路徑

問題

請設計一個函式,用來判斷在一個矩陣中是否存在一條包含某字串所有字元的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則之後不能再次進入這個格子。 例如 a b c e s f c s a d e e 這樣的3 X 4 矩陣中包含一條字串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字串的第一個字元b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入該格子。

解析

首先說明下題目中矩陣的表現形式為什麼可以用一維矩陣表示。我們都知道矩陣的數學定義與資料結構中二維陣列相似,所以一般都是用二維陣列來儲存。二維陣列其實是一維陣列的一維陣列,意思是用二維陣列的第一維大小的一個一維陣列來分別儲存第二維大小的一維陣列物件,這樣在訪問array[3][4]

時,是先在第一個一維陣列訪問array[3],找到下標為3的一維陣列物件,然後在array[3]的一維陣列訪問array[3][4]。其實可以理解為二級查詢,先找到下標為3的一維陣列,然後在這個一維陣列找到下標為4的元素,很顯然這樣的做法需要額外的儲存來存放第二維陣列物件。一般採用陣列壓縮來將二維陣列轉化為一維陣列。

一維陣列a[n]中定址公式為:y,y是陣列a的某個下標。

又因為二維陣列其實是一維陣列的一維陣列,所以二維陣列a[m][n]的定址公式為:x * n + y,x是第一維的下標,y是第二維的下標。顯然我們可以通過將二維陣列的第二維的內容依次按序加入到一個一維陣列,這樣就可以充分利用二維陣列的定址公式在一維陣列中完成二維陣列的效果。

所以題目才給出了a b c e s f c s a d e e這樣3 x 4二維結構的一維形式,其實對應的二維形式為:

a b c e
s f c s
a d e e

再來說說題目的意思,其要求是從矩陣的任意一點出發,能夠找到一個與指定字串序列內容相同的非環狀路徑。

思路一

這種在非固定長度的路徑尋找問題非常適合用回溯法來做,又稱為深度優先遍歷(dfs)。回溯法是思想其實就是暴力窮舉法,它在所有的解空間尋找可行解,只不過它不是盲目的窮舉,而是按照一定規則(也就是深度優先)來搜尋可行解。這種策略廣泛用在搜尋過程中會面臨多種選項的應用問題上。

具體做法是從一個起始點出發,然後從所有的可選方向中選擇一個方向進入下一個節點,之後按照同樣的過程不斷深入,直到到達終態。所謂的終態指的是當前到達的節點正好匹配到指定的字串最後一個字元,或者不匹配當前遍歷到指定字串的字元。此時就應該回溯到上次選擇方向的節點上,選擇另一個方向繼續同樣的探索,若此節點上所有的方向都試了還是失敗的話,要繼續向上回溯,繼續選擇方向。由此可以看出回溯法的複雜度非常高,指數級別的。但如果可以很快探尋出可行解,也可以快速結束遞迴過程,過於依賴問題空間。

另一個要解決就是如何避免進去已探尋過的節點,這我們申請與原問題空間一樣大的訪問陣列,對已訪問過的節點標記為true,未訪問過的節點為false。這樣我們每訪問一個節點,就標記true表示已訪問。注意在向上回溯到父節點時,記得恢復現成,對發生回溯的節點恢復為未標記,以免耽誤父節點另一個方向的探索過程。

	public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        if (matrix == null || str == null
                || rows * cols < str.length) {
            return false;
        }
        boolean[][] flag = new boolean[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (dfs(matrix, i, j, rows, cols, 0, str, flag)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean dfs(char[] matrix, int row, int col, int rows, int cols,
                       int index, char[] str, boolean[][] flag) {
        if (row < 0 || row >= rows || col < 0 || col >= cols
                || matrix[row * cols + col] != str[index] || flag[row][col]) {
            return false;
        }
        if (index == str.length - 1) {
            return true;
        }
        flag[row][col] = true;
        //上下左右試探,若有一個方向是正確的,那麼之後都不遞迴了
        if (dfs(matrix, row + 1, col, rows, cols, index + 1, str, flag)
                || dfs(matrix, row - 1, col, rows, cols, index + 1, str, flag)
                || dfs(matrix, row, col + 1, rows, cols, index + 1, str, flag)
                || dfs(matrix, row, col - 1, rows, cols, index + 1, str, flag)) {
            return true;
        }
        //恢復現場
        flag[row][col] = false;
        return false;
    }

總結

dfs的關鍵就是探索與恢復現場,深刻理解dfs就是自己在本子多畫,新手空想一般很難想通,我就是這麼過來的,加油。

  • List item