题目要求
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
}
}