剑指 Offer 12. 矩阵中的路径

这篇博客探讨了如何利用回溯算法解决矩阵中寻找子字符串的问题,首先介绍了基本的深度优先搜索(DFS)模板,并解释了回溯算法的关键在于状态恢复。博主提供了两种实现方式,第一种通过额外的访问数组记录状态,虽然简洁但消耗额外空间;第二种则是直接修改矩阵,使用'#'标记已访问位置,从而节省空间。两种方法的时间复杂度均为O(4^kMN),空间复杂度分别为O(MN)和O(k)。
摘要由CSDN通过智能技术生成

这里使用了回溯算法,回溯算法是一种比较特别的DFS,它需要在达到搜索条件后,回溯上一次,继续搜索。普通的DFS适合找路径是否存在的问题,而回溯算法适合解决有几条路径的问题。

这里给出DFS的模板

dfs的模板
```cpp
dfs(){

    // 第一步,检查下标是否满足条件

    // 第二步:检查是否被访问过,或者是否满足当前匹配条件

    // 第三步:检查是否满足返回结果条件

    // 第四步:都没有返回,说明应该进行下一步递归
    // 标记
    dfs(下一次)
    // 取消标记,回溯
}  


main() {
    for (对所有可能情况) {
        dfs()
    }
}

先来我自己的解法,设置一个和矩阵同等大小的访问数组,默认为false,表示没有被访问过。然后遍历每个位置,调用DFS。

算法复杂度:

  • 时间复杂度: O ( 4 k M N ) O(4^kMN) O(4kMN)。这里, k k k指的是字符串的长度, M M M N N N分别表示的是矩阵的长和宽。
  • 空间复杂度: O ( M N ) O(MN) O(MN),由于新建了一个同等大小的visited数组。
// 这里需要遍历每个位置,来一个DFS,才能求解,
// 一个DFS是无法求解的
class Solution {
    char[][] board;
    String word;
    boolean visited[][];
    public boolean exist(char[][] board, String word) {
        
        this.board = board;
        this.word = word;
        visited = new boolean[board.length][];
        
        // 这里初始化后默认是false
        for(int i = 0; i < board.length; i++){
            visited[i] = new boolean[board[0].length];
        }
        // for(boolean[] visit : visited)
        //     System.out.println(Arrays.toString(visit));
        for(int i = 0; i < board.length; i++){
            for(int j = 0; j < board[i].length; j++){
                if(exist(i, j, 0))
                    return true;
            }
        }
        return false;
    }

    private boolean exist(int row, int col, int len){
        if(len == word.length())
            return true;
        
        if(row < 0 || row >= board.length || col < 0 || col >= board[0].length || visited[row]              [col] == true || board[row][col] != word.charAt(len))
            return false;
        boolean ret = false;
        visited[row][col] = true;
        ret = exist(row + 1, col, len + 1) || exist(row - 1, col, len + 1) || exist(row, col - 1,        len + 1) || exist(row, col + 1, len + 1);
        // 这里这个恢复为未访问是回溯算法的重点
        visited[row][col] = false;    
        return ret;
    }
}

这样太浪费内存了,我们可以直接修改矩阵,用矩阵自己的值来表示状态。例如,把访问过的矩阵元素设置为#,因为矩阵中不会出现#。所以出现#代表已经被访问。

  • 时间复杂度: O ( 4 k M N ) O(4^kMN) O(4kMN)。这里, k k k指的是字符串的长度, M M M N N N分别表示的是矩阵的长和宽。因为需要对每个元素的4个方向进行长度 k k k的匹配
  • 空间复杂度: O ( k ) O(k) O(k),递归深度最多为字符串的长度

//
class Solution {
    char[][] board;
    String word;
    // 原本打算把len当作成员变量,记录已经匹配字符串长度,但是管理起来一直出BUG
    // int len = 0;
    
    public boolean exist(char[][] board, String word) {
        this.board = board;
        this.word = word;
        
        // 对矩阵每个元素进行DFS,匹配成功则返回true;
        for(int i = 0; i < board.length; i++){
            for(int j = 0; j < board[i].length; j++){
                if(exist(i, j, 0))
                    return true;
            }
        }
        // 都不成功,返回false
        return false;
    }

    private boolean exist(int row, int col, int len){
    	// 检查数组越界以及是否访问以及是否匹配
        if(row < 0 || row >= board.length || col < 0 || col >= board[0].length || board[row]              [col] == '#' || board[row][col] != word.charAt(len))
            return false;
        // 若匹配长度完毕,则返回true
        if(len == word.length() - 1)
            return true;
        
        
        boolean ret = false;
        // 用`#`来记录已经访问状态
        board[row][col] = '#';
        ret =    exist(row + 1, col, len + 1) || exist(row - 1, col, len + 1) 
                || exist(row, col - 1, len + 1) || exist(row, col + 1, len + 1);
        // 回溯,返回默认状态。
        board[row][col] = word.charAt(len); 
        return ret;
    }
}

用C++实现上述思想,不过这次用一个数组记录应该走的位置。不好理解,还是别这么做。

class Solution {
public:
    char *mat = 0;
    int h = 0, w = 0;
    int str_len = 0;
    int dir[5] = {-1, 0, 1, 0, -1};

    bool dfs(int i, int j, int pos, char *str) {
        // 因为dfs调用前,没有进行边界检查,
        // 所以需要第一步进行边界检查,
        // 因为后面需要访问mat中元素,不能越界访问
        if (i < 0 || i >= h || j < 0 || j >= w) {
            return false;
        }

        char ch = mat[i * w + j];
        // 判断是否访问过
        // 如果没有访问过,判断是否和字符串str[pos]匹配
        if (ch == '#' || ch != str[pos]) {
            return false;
        }

         // 如果匹配,判断是否匹配到最后一个字符
        if (pos + 1  == str_len) {
            return true;
        }

        // 说明当前字符成功匹配,标记一下,下次不能再次进入
        mat[i * w + j] = '#';
        for (int k = 0; k < 4; ++k) {
            if (dfs(i + dir[k], j + dir[k + 1], pos + 1, str)) {
                return true;
            }
        } 
        // 如果4个方向都无法匹配 str[pos + 1]
        // 则回溯, 将'#' 还原成 ch          
        mat[i * w + j] = ch;
        // 说明此次匹配是不成功的
        return false;   
    }
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        mat = matrix;
        h = rows, w = cols;
         str_len = strlen(str);

        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                if (dfs(i, j, 0, str)) {
                    return true;
                }
            }
        }
        return false;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值