一步步将dfs回溯剪枝进行优化(LeetCode 79. 单词搜索)

image-20210818102813292

一个极其普通的解法

主要思想是找到可行的起始点 深度遍历搜索 保存当前的字符串如果匹配则继续否则剪枝

class Solution {
    char[][] board;
    boolean res = false;
    int n;
    int m;
    boolean[][] visited;
    public boolean exist(char[][] board, String word) {
        this.board = board;
        n = board.length;
        m = board[0].length;
        visited = new boolean[n][m];
        char c = word.charAt(0);
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(c == board[i][j]){
                    dfs(i,j,"",word);
                    if(res == true)return true;
                }
            }
        }
        return false;
    }
    public void dfs(int row,int column,String cur,String word){
        if(column == -1 || row == -1 || column == m || row == n)return;
        if(visited[row][column]) return;
        cur += board[row][column];
        int length = cur.length();
        if(word.charAt(length - 1) != cur.charAt(length - 1))return;
        if(word.length() == cur.length()) {
            res = true;
            return;
        }
        visited[row][column] = true;
        dfs(row,column + 1,cur,word);
        dfs(row,column - 1,cur,word);
        dfs(row + 1,column,cur,word);
        dfs(row - 1,column,cur,word);
        visited[row][column] = false;  
    }
}

这就是我第一次提交的代码 来看看执行效率

image-20210818121425862

额… 看来这个方案还是有不少优化的余地的 让我们接着往下看

考虑中间过程

​ 在学习动态规划的时候我们知道,很多题目其实只求最终的答案 这种时候我们不必存储中间过程

除非中间过程是一些你要多次求解的数据 此时缓存下来才是对提高时间效率有帮助的

​ 在这里也是,我们没有必要把目前求得的整个字符串存储下来,我们只需要判断当前的相应位置字符是否匹配即可,所以引出优化一:省略中间过程

优化一:省略中间过程

		   //只判断当前位置就好啦 不符合就剪枝
        if(word.charAt(curCount) != board[row][column])return;
class Solution {
    char[][] board;
    boolean res = false;
    int n;
    int m;
    boolean[][] visited;
    public boolean exist(char[][] board, String word) {
        this.board = board;
        n = board.length;
        m = board[0].length;
        visited = new boolean[n][m];
        char c = word.charAt(0);
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(c == board[i][j]){
                    dfs(i,j,0,word);
                    if(res == true)return true;
                }
            }
        }
        return false;
    }
    public void dfs(int row,int column,int curCount,String word){
        if(column == -1 || row == -1 || column == m || row == n)return;
        if(visited[row][column]) return;
        //只判断当前位置就好啦 不符合就剪枝
        if(word.charAt(curCount) != board[row][column])return;
        if(word.length() - 1 == curCount) {
            res = true;
            return;
        }
        visited[row][column] = true;
        dfs(row,column + 1,curCount + 1,word);
        dfs(row,column - 1,curCount + 1,word);
        dfs(row + 1,column,curCount + 1,word);
        dfs(row - 1,column,curCount + 1,word);
        visited[row][column] = false;  
    }
}

image-20210818122411499

时间和空间效率都大大地提升,主要原因是不需要存储大量的中间字符串,以及省去了对字符串进行操作的时间成本

考虑visited数组

visited数组是我定义的记录当前位置的布尔数组,这个能不能也想办法把它省去呢?这里对二维数组的存储和操作也是需要一些成本的

在没有提供原始数组的题目中(如求1…n的排列的所有组合),我们确实没办法,只能手动定义visited数组,但在本题可是有存储单词字母的二维数组的,我们把这个原始数组利用起来就可以省去visited数组的成本!所以引出优化二:省略visited数组

优化二:省略visited数组

这个时候题目的提示信息的重要性就体现出来了

image-20210818123220585

既然原始数组仅由字母组成,那么我们用一个符号"."来表示这个位置已经访问过不能再次使用不就好了?

不过记得保存原始状态然后在dfs之后恢复

char c = board[row][column];
board[row][column] = '.';

//dfs....

board[row][column] = c;  
class Solution {
    char[][] board;
    boolean res = false;
    int n;
    int m;
    char[] arr;
    public boolean exist(char[][] board, String word) {
        this.board = board;
        n = board.length;
        m = board[0].length;
        
        arr = word.toCharArray();
        char c = word.charAt(0);
        //找合适的起始点
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(c == board[i][j]){
                    dfs(i,j,0);
                    if(res == true)return true;
                }
            }
        }
        return false;
    }
    public void dfs(int row,int column,int cur){
        //判断范围
        if(column == -1 || row == -1 || column == m || row == n || cur == arr.length)return;
        //判断是否已访问 是否匹配 剪枝
        if(arr[cur] != board[row][column]) return;
        if(cur == arr.length - 1) {
            res = true;
            return;
        }
        char c = board[row][column];
        board[row][column] = '.';
        dfs(row,column + 1,cur+1);
        dfs(row,column - 1,cur+1);
        dfs(row + 1,column,cur+1);
        dfs(row - 1,column,cur+1);
        board[row][column] = c;  
    }
}

image-20210818123424369

这个优化同样可以用在如迷宫这种提供了原始数组的类似题目
到这里我们的dfs回溯剪枝优化就完成得差不多啦!

可以看到我的原始解法其实和最终优化后的解法的区别不大,但就是这些小小的细节可以大大提高我们算法的效率!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值