地上有一个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。请问该机器人能够到达多少个格子?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof
很明显,这一道题考查的是深度优先搜索/广度优先搜索。
方法一:深度优先搜索(通过栈来实现)
1、定义一个栈
2、将当前位置压入栈中
3、当栈不为空的时候,不断获取栈顶元素,判断从栈顶元素开始走的下一步位置是否可以走,如果可以,那么就将这个位置压入栈中,否则,如果所有的下一步位置都不可以走,就从这个栈中跳出栈顶元素
。
如何判断栈顶元素的下一步是否可以走呢?题目中已经明显给出了要求,不可以移动到方格外,也不能进行行坐标和列坐标的数位之和大于k,同时我们已经走过的位置同样不可以走,基于此,我们判断下一步位置是否可以走。
如果这个位置不可以走,我们如何调整走向呢?我们将定义两个二维数组,move_x,move_y,并且长度是2,并且move_x的初始值为{0,1},move_y的初始值为{1,0},因为题目是从[0,0]到[m - 1,n - 1],所以我们定义它移动的方向是右,下两个方向,每一次调整位置,都是在栈顶元素的基础上进行调整位置的
。
4、重复上述操作,直到栈为空。
对应的代码:
class Solution {
//由于题目要求是从下标[0,0]到达[m - 1,n - 1],那么就说明了只要2个方向即可实现
int[] move_x = new int[]{0,1};
int[] move_y = new int[]{1,0};
Stack<int[]> stack = new Stack<int[]>();//栈的泛型是一个数组类型,存放的是当前位置的横坐标和纵坐标
//利用深度优先搜索实现
public int movingCount(int m, int n, int k) {
if(k == 0)
return 1;//如果k为0,那么只有[0,0]这个位置可以满足,所以直接返回1
int[][] route = new int[m][n]; //标记当前位置是否已经走过了
int arr[];
int i,x,y,a,b,result; //x,y表示机器人当前的位置,a,b表示机器人的下一步位置
boolean flag ; //判断机器人的下一步位置是否可以走
stack.push(new int[]{0,0});
result = 1;
route[0][0] = 1;//值为1表示已经走过了
while(!stack.empty()){
//当栈不为空时,获取栈顶节点
flag = false;
arr = stack.peek();
x = arr[0];
y = arr[1];
for(i = 0; i < 2; i++){
a = x + move_x[i];
b = y + move_y[i];
if(a < 0 || a >= m || b < 0 || b >= n || route[a][b] == 1) //如果下一步发生了越界或者已经走过了,直接跳过,执行i++
continue;
else{
/*否则我们需要讨论这个位置的坐标各个数字之和是否大于k,如
果是,那么直接标记这一步已经走过了,这样下一次来的时候不
需要再判断这一个位置了,否则,就将这个位置压入到栈中
*/
route[a][b] = 1;
if(get(a) + get(b) <= k){
stack.push(new int[]{a,b});
flag = true;
result++;
break;
}
}
}
if(!flag) //如果当前栈顶元素的下一个位置都不可以走,那么就将这个栈顶元素从栈中跳出
stack.pop();
}
return result;
}
public int get(int num){
//获取num这个数的各个数位的和
int sum = 0;
while(num != 0){
sum += num % 10;
num /= 10;
}
return sum;
}
}
方法二:通过递归的方式实现深度优先搜索
class Solution {
//由于题目要求是从下标[0,0]到达[m - 1,n - 1],那么就说明了只要2个方向即可实现
int[] move_x = new int[]{0,1};
int[] move_y = new int[]{1,0};
int[][] route; //标记当前位置是否已经走过了
int result = 0;
public int movingCount(int m, int n, int k) {
if(k == 0)
return 1;//如果k为0,那么只有[0,0]这个位置可以满足,所以直接返回1
route = new int[m][n];
getResult(0,0,m,n,k);
return result;
}
public void getResult(int x,int y,int m,int n,int k){
result++;
route[x][y] = 1;//标记当前位置已经访问过了
int a,b,i;//a,b表示当前位置的下一步位置
for(i = 0; i < 2; i++){
//获取下一步位置
a = x + move_x[i];
b = y + move_y[i];
if(a < 0 || a >= m || b < 0 || b >= n || route[a][b] == 1){
continue;//如果当前这个位置发生了越界,或者已经走过了
}else if(get(a) + get(b) > k){
route[a][b] = 1; //下一步位置虽然没有越界,但是各个数位之和大于k,那么标记这一步走过了,下一次就可以不用走这一步了
continue;
}
getResult(a,b,m,n,k);//如果下一步可以走,那么就进入递归
}
}
public int get(int num){
//获取num这个数的各个数位的和
int sum = 0;
while(num != 0){
sum += num % 10;
num /= 10;
}
return sum;
}
}
运行结果:
很奇怪的是,我们使用递归的时候,必然需要考虑的就是递归结束的条件,但是我们这里进行递归的时候却没有使用,这是为什么呢?我们可以通过观察一下for循环里面的判断语句,如果当前位置的下一步位置是可以走的,才可以进行递归,那么当最后一次递归的时候,所有的位置都已经走过了,从而使得程序退出的是for循环,来结束递归。