地上有一个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
解题思路:本题和link类似,属于典型搜索回溯问题,所以可以用深度优先搜索和广度优先搜索结局
首先题目给出了一个每个坐标数位和概念,就是一个数每位数字求和
int sums(int x)
int s = 0;
while(x != 0) {
s += x % 10;
x = x / 10;
}
return s;
根据题意,因为是计数,所以机器人是不能走已经走过的路的,因为是从左上角进行遍历的,所以只能走下和右两个路径
方式一:深度优先遍历DFS
- 深度优先遍历:可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
- 剪枝:在搜索过程中,如遇到数位和超出目标,或者元素已经被访问,则直接返回
算法解析:
- 递归参数: 当前元素在矩阵中的行列索引 i 和 j ,矩阵行和列m,n,目标。
- 终止条件: 当 ① 行列索引越界 或 ② 数位和超出目标值 k 或 ③ 当前元素已访问过 时,返回 00 ,代表不计入可达解。
递推工作:
- 标记当前单元格 :将索引 (i, j) 存入visited 中,代表此单元格已被访问过。
- 搜索下一单元格: 计算当前元素的 下、右 两个方向元素的数位和,并开启下层递归 。
回溯返回值: 返回 1 + 右方搜索的可达解总数 + 下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数
代码:
class Solution {
int m, n, k;
boolean[][] visited; //辅助矩阵,标记是否已经被访问过了
public int movingCount(int m, int n, int k) {
this.m = m;
this.n = n;
this.k = k;
this.visited = new boolean[m][n];
return dfs(0, 0, m, n, k);
}
/**
* @param i 元素在矩阵中的索引行
* @param j 元素在矩阵中的索引列
* @param m 矩阵行数
* @param n 矩阵列数
* @param k 目标数位和
* @return 递归中止条件:1.索引行或列越界
* 2.位数和超出目标值k
* 3.当前元素已经被访问过,返回0,代表不计入可达解
*/
public int dfs(int i, int j, int m, int n, int k) {
if (i >= m || j >= n || k < sums(i) + sums(j) || visited[i][j]) return 0;
visited[i][j] = true;
return 1 + dfs(i + 1, j, m, n, k) + dfs(i, j + 1, m, n, k);
}
//求数位和函数
int sums(int x) {
int s = 0;
while (x != 0) {
s += x % 10; //得到x个位数字
x = x / 10; //另x的十位数字向右移一位,即删除个位数字
}
return s;
}
}
方式二:广度优先遍历BFS
- BFS和DFS两者目标都是遍历矩阵,但是方法不同,DFS是按照一个方向走到底,再回退,以此类推;BFS是按照"平推的方式"进行搜索
- BFS通常利用队列来实现广度优先搜索
算法解析
- 初始化:将机器人初始点 (0, 0)(0,0) 加入队列
queue
; - 递归终止条件:
queue 为空
。代表已遍历完所有可达解。 - 迭代流程:
- 单元格出队:将队首单元格的 索引、数位和 弹出,作为当前搜索单元格
- 判断是否跳过:若 ① 行列索引越界 或 ② 数位和超出目标值
k
或 ③ 当前元素已访问过 时,执行continue
- 标记当前单元格:将单元格索引 (i, j) 存入
visited
中,代表此单元格 已被访问过 。 - 单元格入队:将当前单元格下方和右方加入
queue
- 返回值:使用辅助变量res自增记录解的数量
代码:
public int movingCount(int m, int n, int k) {
boolean visited[][] = new boolean[m][n];
int res = 0;
//队列保存的是访问到的格子的坐标,是一个二维数组
Queue<int[]> queue = new LinkedList<int[]>();
queue.add(new int[]{0, 0}); //将第一个元素入队
//队列为空时,代表已经完成所有可达解
while (queue.size() > 0) {
int[] x = queue.poll(); //将队首的元素即第一个单元格弹出,获得她的索引,作为当前搜索单元格
int i = x[0], j = x[1];
if (i >= m || j >= n || k < sums(i) + sums(j) || visited[i][j]) { //不满足条件,就跳过这个格子
continue;
}
visited[i][j] = true; //标注已经被访问过
res++; //结果集+1
queue.add(new int[]{i + 1, j}); //将下方和右方元素继续入队
queue.add(new int[]{i, j + 1});
}
return res;
}
//求数位和函数
int sums(int x) {
int s = 0;
while (x != 0) {
s += x % 10; //得到x个位数字
x = x / 10; //另x的十位数字向右移一位,即删除个位数字
}
return s;
}