剑指Offer-67-矩阵中的路径

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/84195991

项目地址:https://github.com/SpecialYy/Sword-Means-Offer

问题

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 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

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/84195991