【leetcode -C++】-DFS类型- 980 unique path 3

这篇博客介绍了LeetCode 980题目的解决方案,探讨了如何使用深度优先搜索(DFS)和动态规划(DP)方法在二维网格中找到从起点到终点的独特路径。博客详细解释了两种方法的思路和实现,重点阐述了如何避免重复状态并优化空间复杂度。
摘要由CSDN通过智能技术生成

题目要求

On a 2-dimensional grid, there are 4 types of squares:

  • 1 represents the starting square.  There is exactly one starting square.
  • 2 represents the ending square.  There is exactly one ending square.
  • 0 represents empty squares we can walk over.
  • -1 represents obstacles that we cannot walk over.

Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly 

数据范围:

1 <= grid.length * grid[0].length <= 20

给定一个二维矩阵,矩阵元素为1的位置是入口,元素为2的是出口,0代表可以走的位置,-1代表障碍。

返回从入口走向出口的不同方案(求解从起点到终点有效路径个数),要求非障碍位置(非-1位置)都必须走过,并且一条路径不能重复走某一个节点。

思路解析

本题首先要遍历矩阵,判断入口出口的位置以及可以走的位置有多少个。

1、dfs回溯

本题可以用dfs暴力搜索来求解,对于当前可落脚的位置,锁定当前位置为障碍位置,并向上、下、左、右四个方向继续走,之后记得还原当前位置。dfs递归的返回条件包括:当前位置为障碍或者这条路径走过的节点;当前位置到达终点。

时间复杂度:O(),空间复杂度O(R*Q)

代码实现

class Solution {
public:
    int res = 0;int ex,ey,sx,sy,m,n; int empty = 1;//把入口点也算上
    int uniquePathsIII(vector<vector<int>>& grid) {
        //计数可以走的空位置和起始、结束位置
        m = grid.size();n = grid[0].size();
        for(int i = 0;i<m;i++){
            for(int j = 0;j<n;j++){
                if(grid[i][j] == 0) empty++;
                if(grid[i][j] == 1)//
                {
                    sx =i ;
                    sy = j;
                }
                if(grid[i][j]==2){
                    ex = i;
                    ey = j;
                }
            }
        }
      dfs(grid,sx,sy);
      return res;  
    
    }
    bool check(vector<vector<int>> &grid,int x,int y){
        return x>=0 && x<m && y>= 0 && y<n && grid[x][y]>=0;
    }
    void dfs(vector<vector<int>> &grid,int x0,int y0){
        if(check(grid,x0,y0)== false){
            return ;
        }
        if(x0 == ex && y0 == ey && empty == 0){//找到一条路径
           
            res ++;
            return ;
        }
            //在将(x0,y0)作为路径的一个节点时,可选的empty数减少
            grid[x0][y0] = -2;//锁定当前位置,设置成-2做特殊意义,设置成-1也能过
            empty --;//空位置少了一个
            dfs(grid,x0+1,y0);
            dfs(grid,x0,y0+1);
            dfs(grid,x0-1,y0);
            dfs(grid,x0,y0-1);
            empty++;//遍历完成后释放。
            grid[x0][y0] =0;
        
    }
    
};

回溯法可能有重复的状态计算,因此用动态规划法来保留状态

2、动态规划法

用数组记下某状态往下走,走到出口的所有路径数,dp[i][j][empty],i,j为位置,empty为当前能走的位置(这里需要位运算)。

状态dp[x][y][empty]指的是当前在(x,y)的位置,所有还需走的点的位置为empty的情况下,走到终点有多少路径。

不好理解?举个栗子。

首先我们要用一个数来代表矩阵中能走的位置。矩阵的位置可以由二元组(i,j)来表示,可以参考二维数组的内存表示,将二元组映射到一个数上。

如矩阵A={{1,0,2}},1是入口,0和2的位置都可以落脚。0的坐标是(0,1),2的坐标是(0,2),

通过i<< x * n + y 将二维坐标(x,y)映射到一维数字上,n是矩阵的列数。则0对应“10”,2对应“110”

用empty表示当前能走的位置,empty初始化为0,遍历过程中和10做异或,empty = 10;再和110 做异或为110.

dp[0][0][110]指的是在入口(0,0)位置,上下左右探索(只能向右走),注意在check()函数中empty&core(x,y)用来判断(x,y)是否可以访问,如入口右边位置(0,1)通过core函数映射到一维是1,此时empty为110, 1 & 110 不为0,因此可以从入口点向右走。

然后以(0,1)为当前位置继续递归,注意传到下一层递归函数的empth = empty^core(0,1)即 110^10 为100.( 倒序来看,倒数第二位变为了0,倒数第三位为1,代表着矩阵的第3个数是可以访问的)

对于(0,1)的左边位置,empty = 100,core(0,0) = 1,empty & core(0,0) = 0,check函数返回false

对于(0,1)的右边位置(其他位置被访问过或超出矩阵范围),empty = 100,core(0,2) = 100,empty  & core(0,2)= 100,check函数返回true,可以走。并在下一步递归中判断出(0,2)位置是终点, 返回1,表示当前是一条可用路径。

在递归上层对于四个方向都进行判断,对于可以走的方向继续递归走,并在走到终点时返回1,在判断四个方向的递归中进行求和,记录从位置(x,y)开始,在empty的可走位置分布情况下有多少种可能到达终点。并返回这个值到上层递归函数。

代码实现

 

class Solution {
  
    Integer[][][] dp;
    int r[] = {0,1,0,-1};
    int c[] = {-1,0,1,0};
    int m; int n;  
    int empty = 0,sx = 0 ,sy = 0,ex = 0,ey = 0;
    public boolean check(int x,int y,int empty){//判断(x,y)的位置能否落子,没有超出矩阵范围,这个点在当前路径没有走过
        return x>=0 && x<m && y>=0 && y<n && (empty & core(x,y))!=0;
    }
    public int dfs(int x0,int y0,int empty){
        if(dp[x0][y0][empty]!=null)
            return dp[x0][y0][empty];
        if(x0 == ex && y0 == ey)
                return (empty ==0)?1:0;
       int ans  = 0;int x1,y1;
       for(int i = 0;i<4;i++){
           x1 = x0+r[i];
           y1 = y0+c[i];//判断当前值是否有效
           if(check(x1,y1,empty))
           ans += dfs(x1,y1,empty ^ core(x1,y1));//empty中(x0,y0)的位置如果为1,则结果为0证明该位置被占。
       }
       dp[x0][y0][empty] = ans;
       return ans;
    }
    public int uniquePathsIII(int[][] grid) {
        
    	
    	  m = grid.length; n = grid[0].length;
        dp = new Integer[m][n][1<<m*n];
        for(int i = 0;i<m;i++){
            for(int j = 0;j<n;j++){
                if(grid[i][j]%2 == 0)
                    empty|=core(i,j);//|= 为异或操作,empty在(i,j)的位置为1
                if(grid[i][j] == 1)//起点位置
                {
                    sx =i ;
                    sy = j;
                }
                if(grid[i][j]==2){
                    ex = i;
                    ey = j;
                }
            }
        }
        return dfs(sx,sy,empty);
    }
    public int core(int i,int j){
        return 1<<(n*i+j); // 设置第i行第j列的标志位为1
    }
  
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值