这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
机器人的运动范围
地上有一个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
题解
方法一:深度优先遍历——Java
该题与昨日的题目属于同类题型。
深度优先搜索: 可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为 可行性剪枝 。
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return movingCountHelp(0, 0, k, visited);
}
private int movingCountHelp(int i, int j, int k, boolean[][] visited) {
int sum = movingCountSum(i) + movingCountSum(j);
if (i >= visited.length || j >= visited[0].length ||
sum > k || visited[i][j]) {
return 0;
}
visited[i][j] = true;
return 1 + movingCountHelp(i + 1, j, k, visited) + movingCountHelp(i, j + 1, k, visited);
}
private int movingCountSum(int num) {
int sum = 0;
while (num != 0) {
sum += num % 10;
num /= 10;
}
return sum;
}
}
时间复杂度:O(MN)
空间复杂度:O(MN)
方法一:深度优先遍历——Go
func movingCount(m int, n int, k int) int {
vis := make([][]bool, m)
for i, _ := range vis {
vis[i] = make([]bool, n)
}
return dfs(0, 0, m, n, k, vis)
}
func dfs(i, j, m, n, k int, vis [][]bool) int {
if i<0 || i>=m || j<0 || j>=n || (i/10+i%10+j/10+j%10) > k || vis[i][j] {
return 0
}
vis[i][j] = true
return dfs(i+1, j, m, n, k, vis) + dfs(i-1, j, m, n, k, vis) +
dfs(i, j+1, m, n, k, vis) + dfs(i, j-1, m, n, k, vis) + 1
}
方法二:宽度优先遍历——Java
我们将行坐标和列坐标数位之和大于 k 的格子看作障碍物,那么这道题就是一道很传统的搜索题目,我们可以使用广度优先搜索或者深度优先搜索来解决它,本文选择使用广度优先搜索的方法来讲解。
那么如何计算一个数的数位之和呢?我们只需要对数 x 每次对 10 取余,就能知道数 x 的个位数是多少,然后再将 x 除 10,这个操作等价于将 x 的十进制数向右移一位,删除个位数(类似于二进制中的 >> 右移运算符),不断重复直到 x 为 0 时结束。
同时这道题还有一个隐藏的优化:
我们在搜索的过程中搜索方向可以缩减为向右和向下,而不必再向上和向左进行搜索。 16 * 16 的地图随着限制条件 k 的放大,可行方格的变化趋势,每个格子里的值为行坐标和列坐标的数位之和,蓝色方格代表非障碍方格,即其值小于等于当前的限制条件 k。
我们可以发现随着限制条件 k 的增大,(0, 0) 所在的蓝色方格区域内新加入的非障碍方格都可以由上方或左方的格子移动一步得到。而其他不连通的蓝色方格区域会随着 k 的增大而连通,且连通的时候也是由上方或左方的格子移动一步得到,因此我们可以将我们的搜索方向缩减为向右或向下。
class Solution {
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, 0, 0 });
while(queue.size() > 0) {
int[] x = queue.poll();
int i = x[0], j = x[1], si = x[2], sj = x[3];
if(i >= m || j >= n || k < si + sj || visited[i][j]) continue;
visited[i][j] = true;
res ++;
queue.add(new int[] { i + 1, j, (i + 1) % 10 != 0 ? si + 1 : si - 8, sj });
queue.add(new int[] { i, j + 1, si, (j + 1) % 10 != 0 ? sj + 1 : sj - 8 });
}
return res;
}
}