(14/31)剑指 Offer: 12&13.搜索与回溯算法(中等)

 1.剑指offer:12题.矩阵中的路径

力扣https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof/    

方法:回溯+dfs

        矩阵中是否存在符合word字符串的路线。利用dfs递归每个格是否有符合word字符串路线的可能性。每次格的走的方向有四个,且题中不让重复走格,故可标记走过的格字符为特殊字符,此后再遇到直接跳过走该格的可能性。

算法思路

        1. 构造dfs函数,遍历每个格是否有符合word字符串路线的可能性。

                1> 函数构造

                        i. 函数返回类型:bool型,当该格符合题目时,返回true

                        ii. 函数参数:5个。board:矩阵数组,word:要求的字符串,u:字符串word下标为u的字符,x:当前格的行,y:当前格的列。

                由此,dfs函数的意义是,判断矩阵格(x,y)的字符与word[u]的字符是否相同。其具体形式为:bool dfs(vector<vector<char>>& board, string& word, int u, int x, int y)

                2> 函数内容

                        i. 当前格字符不是word[u]字符        返回false

                        ii. 当前格字符是word[u]字符 

                                ①u是word字符串最后一个字符下标,说明找到符合路线的格,返回true。

                                ②u不是字符串最后一个字符下标

                                        开始走格。判断接下来是否有符合的路线。

                3> 走格的具体步骤

                        i. 先记录当前格字符为t,后标记该格为特殊字符

                        ii. 判断能走哪些方向

                                利用for循环遍历4次,依次判断该方向是否能走。先在全局创建两个数组作为行和列的偏移情况:向上(-1,0),向下(1,0),向左(0,-1),向右(0,1),每次循环,对当前格的行和列进行加减(即向该方向的移动),在不符合的情况(如走出矩阵,走过该格)选择跳过该次循环,进入下个方向的判断。

                                如果找到符合word[u]的情况,就调用dfs函数,判断该格是否有某个方向的字符与word[u+1]相同,以此循环,不符合即返回false。

        2. 在函数exist中,遍历每个格的可能性。         

代码

代码作者:力扣用户@林小鹿

class Solution {
public:
    //方向偏移数组
    int dx[4]{0,0,1,-1};
    int dy[4]{1,-1,0,0};
    // 判断在矩阵格(x,y)的字符是否与word[u]相同
    bool dfs(vector<vector<char>>& board, string& word, int u, int x, int y){
        if(board[x][y] != word[u])  return false;    // 字符不同,返回false
        if(u == word.size() - 1)    return true;    // 字符相同且u为最后下标,说明路线完整,成功返回true    
        char t = board[x][y];    // 记录当前格字符
        board[x][y] = '.';        // 标记当前格字符为特殊字符,以免在接下来重复走格

        for(int i = 0; i < 4; ++i){    // 判断何种方向上的字符符合路线
            int a = x + dx[i], b = y + dy[i];    // 不同方向的行与列的偏移
            // 跳过情况:格(a,b)或在矩阵外,或已经走过
            if(a >= board.size() || a < 0 || b >= board[0].size() || b < 0 || board[a][b] == '.')   continue;
            // 当前格字符符合路线,判断是否有下个格符合
            if(dfs(board,word,u+1,a,b)) return true;
        }
        board[x][y] = t;    // 将更改为特殊字符的格变回原字符
        return false;    // for循环后没有符合的方向,返回false
    }
    bool exist(vector<vector<char>>& board, string word) {
        // 遍历每个格是否有符合路线的可能性
        for(int i = 0; i < board.size(); ++i)
            for(int j = 0; j < board[0].size(); ++j)
                if(dfs(board,word,0,i,j))   return true;
        return false;
    }
};

复杂度分析

        设字符串长度为k。

        时间复杂度:O(m*n*3^k)。因为需要遍历矩阵中每个格是否存在路线,所以需要遍历m*n次,且每次遍历需要利用dfs函数遍历当前格的4个方向,题目中说不能走回头路,所以每次实际需要遍历3次。

        空间复杂度:O(k)。递归的深度最深不会超过k。

算法流程图

 

 

 

 

 

 

2.剑指offer:13题.机器人的运动范围

力扣https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/?envType=study-plan&id=lcof&plan=lcof&plan_progress=ykzzr6s

 方法:dfs

        因为机器人从(0,0)开始运动,只能向下走或向右走,假设向下走到的格为a,向右走的格为b,又可以以a和b作为原地只能向下或向右走,以此类推。

        题解的关键之一是如何计算当前格横纵坐标的每位数之和,由于此题的横纵坐标大小限制在[1,100],而在该范围内有一个适用的计算规律:已知向下或向右会使纵坐标或横坐标+1,所以其横纵坐标每位数总和一般也只+1,如(2,3)那么走一格位数总和由5变成6,但如果+1后位数总和变成一个整数,如(10,9)向右移动变(10,10)总和由10变成2,减了8,公式表达即为:

        

        所以,从(0,0)开始走,每次机器人都判断下方和右方是否能走,如果不能返回0,反之,返回1 + 该格向下走可能走的格数 + 该格向右走可能走的格数,由此,展开递归。

算法思路

        1. 创建一个和地上方格大小一致的bool型数组,用来记录当前是否能够走,能走记为true。

        2. 构造dfs函数,用来计算格数。

                1> 函数构造

                        i. 函数返回类型为int,返回可走几格

                        ii. 函数参数有8个。i:当前个的行数,j:当前格的列数,si:行数的每位数和,sj: 列数的每位数和,visited数组:记录当前格是否可走,m:方格行数,n:方格列数,k:题目要求的不可超过和数

                函数全称:int dfs(int i, int j, int si, int sj,vector<vector<bool>>& visited,int m, int n, int k)

                2> 限制条件

                        坐标越出方格,行和列的位数和大于k,visited的当前格为真(即当前格已经走过),都表示不能走,返回0

                3> 标记当前格为真,即走过该格

                4> 返回当前的格数1 + 该格向下走可能走的格数 + 该格向右走可能走的格数

        3. movingCount函数返回dfs函数从(0,0)开始的情况(dfs会遍历每个格可能性)

代码

代码作者:力扣用户@Krahets。

class Solution {
public:
    int movingCount(int m, int n, int k) {
        vector<vector<bool>> visited(m,vector<bool>(n,0));    // 用来记录每个格是否走过
        return dfs(0,0,0,0,visited,m,n,k);
    }    
    // 判断(i,j)是否能走,并递归判断其下格和右格
    int dfs(int i, int j, int si, int sj,vector<vector<bool>>& visited,int m, int n, int k){
        // 越出方格矩阵,位数和大于k,当前格走过 都返回0
        if(i >= m || j >= n || k < si + sj || visited[i][j])    return 0;
        visited[i][j] = true;    // 更新状态当前格(i,j)为走过
        // 返回当前的一个格数(可以走),再判断其下格和右格的情况
        return 1 + dfs(i + 1, j, (i + 1) % 10 != 0 ? si + 1 : si - 8, sj, visited, m, n, k) + dfs(i, j + 1, si, (j + 1) % 10 != 0 ? sj + 1 : sj - 8, visited, m, n, k);
    }
};

复杂度分析

        假设方格矩阵行数为M,列数为N。 

        时间复杂度:O(MN)。最坏情况可能每个格都可走一次,所以遍历M*N次。

        空间复杂度:O(MN)。利用与方格矩阵大小一致的空间来记录每个格是否能走。

算法流程图

 

 

 

 


23. 1.13       第十四次

        身为回溯小白的感受:写不来回溯没关系,多看几遍题解,第一遍先抄题解,理解后,开始自己重写一遍,写错了,对照题解,想一想错误的原因。基本就是参数的作用或表达式的意义理解错误。换而言之,就是对题解的理解不到位。没关系,理清后再写一遍,反复如此,最后写出正解也不要觉得这道题就拿下了,最好定期复习,日积月累,才会真正掌握。

闲谈:

        今天的题目对我来说很难,没有写出来一道。。或者说,我看了题目就觉得自己写不来,就直接看题解了,这是有一定弊端的,以后还是要敢于多多尝试。题目难嘛,自然写博客也不如往日快了,何况我也不能全程专心致志地思考(总有一些意外的干扰),所以今天写完的时间真的超级晚,不过没关系,遇到不会的题就是我提升的大好机会!现在写不来,不意味着以后写不来,为了多多理解,就要多多定期练习,才能巩固。而且最近有点沉迷于看故事,有些故事真是太精彩了,你永远不知道下一个会不会更精彩,所以一直看下去。。这也导致了我在刷题时的分心,提醒自己,再精彩也不是自己的人生,但我可以持续努力,试着构建一个精彩的人生!当下就是最重要的时刻!

        最后,本文如有问题,欢迎指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值