机器人的运动范围
题目:地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
1 <= n,m <= 100
0 <= k <= 20
解题思路
这道题有点类似于上一题的矩阵路径问题。这道题也是在格子中移动,并且移动到达过的坐标不能再次进入。所以这题我们可以用两种图的遍历的方法实现。分别为利用递归实现的深度优先遍历以及队列实现的广度优先遍历。
一、递归实现的深度优先遍历
深度优先遍历(DFS)
(1)从某个顶点V出发,访问顶点并标记为已访问
(2)访问V的邻接点,如果没有访问过,访问该顶点并标记为已访问,然后再访问该顶点的邻接点,递归执行
如果该顶点已访问过,退回上一个顶点,再检查该顶点的邻接点是否都被访问过,如果有没有访问过的继续向下访问,如果全部都访问过继续退回到上一个顶点,继续同样的步骤。
1. 选择V1为出发点,访问V1,然后访问V1的邻接点,邻接点有V2 V3和V4,假设都从左边的邻接点开始访问
2. 选择V1为出发点,访问V1,然后访问V1的邻接点,邻接点有V2 V3和V4,假设都从左边的邻接点开始访问
3. 访问V1的最左边的邻接点V2,访问V2的邻接点V5
4. 访问V5的邻接点V3,V3的左边邻接点是V1,V1已经访问过,所以访问V4
5. V4的左边邻接点V6,访问V6,V6的所有邻接点都已访问过,退回V4,V4的所有邻接点也都访问过退回V3,V3的邻接点全部访问过退回V5
6. V5的邻接点全部访问过退回V2,V2的邻接点全部访问过退回到出发点V1,V1的全部邻接点访问过,遍历结束。
遍历序列为V1→V2→V5→V3→V4→V6
代码如下(详细注释):
class Solution {
public:
int movingCount(int m, int n, int k) {
//创建一个二维数组将其元素全部赋值为false
vector<vector<bool>>my_arry(m,vector<bool>(n,false));
return dfs(0,0,m,n,k,my_arry);
}
private:
//dfs(递归方式)
int dfs(int i,int j,int m,int n,int k,vector<vector<bool>>&my_arry)//注意:此处一定要引用的方式
{
//判断如果元素出界或者行坐标和列坐标的数位之和大于k或者该坐标被遍历过,返回0
if(i<0||i>=m||j<0||j>=n||(i/10+i%10+j/10+j%10)>k||my_arry[i][j])
return 0;
//将遍历过后的坐标元素改为true
my_arry[i][j]=true;
//递归计算上下左右四个方向符合坐标的总数(+1是(0,0)点坐标)
return dfs(i+1,j,m,n,k,my_arry)+dfs(i-1,j,m,n,k,my_arry)+
dfs(i,j+1,m,n,k,my_arry)+dfs(i,j-1,m,n,k,my_arry)+1;
}
};
执行用时:4 ms, 在所有 C++ 提交中击败了72.94%的用户
内存消耗:7.1 MB, 在所有 C++ 提交中击败了32.43%的用户
注意事项
代码中dfs函数中的二维数组my_arry,一定要用传引用参数。一直没发现这个错误,找了好久才发现!!!
因为my_arry是一个普通二维数组(不是动态数组),所以如果不加’&’,就会是以值传递的形式传进来,那么不管dfs函数体里面怎么操作my_arry数组,它的值都不会被真正改变到。
所以必须把my_arry数组的内存地址传进来,这样每次访问完一个元素,把my_arry[m][n]的值设为false,才能真正改变my_arry[m][n]的值。
二、队列实现的广度优先遍历
广度优先遍历(BFS)
(1)从某个顶点V出发,访问该顶点的所有邻接点V1,V2…VN
(2)从邻接点V1,V2…VN出发,再访问他们各自的所有邻接点
(3)重复上述步骤,直到所有的顶点都被访问过
1. 从顶点V1出发,v1入队,访问V1,V1的邻接点有V2 V3 V4,将它们入队,v1出队—(队列:V2 V3 V4)
2. 访问队头V2,V2的邻接点为V1(已访问过,忽略)和V5,将V5入队,V2出队—(队列:V3 V4 V5)
3. 访问V3,V3的邻接点为V1(访问过忽略) V4(已在队列忽略) V6 V5(已在队列,忽略),V6入队,V3出队—(队列:V4 V5 V6)
4. …
代码如下(详细注释):
class Solution {
public:
//求行坐标和列坐标的数位和
int get_sum(pair<int,int>p)
{
int s=0;
while(p.first)
{
s+=p.first%10;
p.first/=10;
}
while(p.second)
{
s+=p.second%10;
p.second/=10;
}
return s;
}
int movingCount(int m, int n, int k) {
int res=0;
//判断边界条件
if(!m||!n) return 0;
//创建数组用来存储坐标点
queue<pair<int,int>>q;
//创建一个二维数组将其元素全部赋值为false
vector<vector<bool>>st(m,vector<bool>(n,false));
//用来实现坐标的上下左右实现
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
//将第一个(0,0)坐标入队,使其进入循环
q.push({0,0});
while(q.size())
{
//取出对头元素
pair<int,int>t=q.front();
//将对头元素出队
q.pop();
//如果该坐标为true代表遍历过了,以及和大于k时跳过该次循环
if(st[t.first][t.second]||get_sum(t)>k) continue;
//否则将总数+1
res++;
//再将遍历过后的坐标赋true,表示不能再别遍历
st[t.first][t.second]=true;
//上下左右移动,将符合的坐标入队
for(int i=0;i<4;i++)
{
int x=t.first+dx[i],y=t.second+dy[i];
if(x>=0 && x<m && y>=0 && y<n) q.push({x,y});
}
}
//返回满足的坐标总数
return res;
}
};
执行用时:8 ms, 在所有 C++ 提交中击败了28.52%的用户
内存消耗:7.3 MB, 在所有 C++ 提交中击败了20.33%的用户