目录
刷题日期:19:3301 星期二2021年3月23日
个人刷题记录,代码收集,来源皆为leetcode
经过多方讨论和请教,现在打算往Java方向发力
主要答题语言为Java
题目:
剑指 Offer 13. 机器人的运动范围
难度中等253
地上有一个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
题目分析
根据提示,限定了坐标轴最大值为100,而最大步数为20。
在坐标轴上绘制一个半径为20的四分之一圆,即是该机器人的活动范围。
m,n>k+1, 且k=0时,结果必为1,k=1时,结果必为3,k = 2时,则为8,为(k+1)^2-1
若m,n<=k+1,则结果为m*n-1
若m<k+1<n,则结果的规律也能找到,这种分类也算是一种解题思路吧,没必要用12题全部比较一番。
初始解答:
书上的代码依旧非常繁琐,看评论有没有和自己分析类似的思路。
参考了方法一。
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];//重新定义一个存放是否可达的矩阵
return dfs(0, 0, m, n, k, visited); // 直接 进入函数
}
private int dfs(int i, int j, int m, int n, int k, boolean visited[][]) {
if(i < 0 || j < 0 || i >= m || j >= n | (i/10 + i%10 + j/10 + j%10) > k || visited[i][j] ) return 0; // 如果索引超出边界,坐标和大于k,已经来过,都返回0
//取坐标值之和的方法很巧妙,值得学习
visited[i][j] = true;
return dfs(i+1, j, m, n, k, visited) + dfs(i-1, j, m, n, k, visited) + dfs(i, j+1, m, n, k, visited) + dfs(i, j-1, m, n, k, visited) + 1; //不加1就白找了
}
}
执行结果:
通过
显示详情
执行用时:1 ms, 在所有 Java 提交中击败了84.99%的用户
内存消耗:35.5 MB, 在所有 Java 提交中击败了57.17%的用户
继续发现其他方法,这种方式更简洁,全部遍历一遍比较粗暴,效果是不错的,参考了方法二
乍一眼看过去然后尝试复现,发现在有些地方结果计算出来是错误的,但是题目的判断条件就是横纵坐标小于k值,没想清楚哪里有问题。
class Solution {
public int movingCount(int m, int n, int k) {
int sum = 0;
for(int i = 0; i <m; i++) {
for(int j = 0; j <n; j++) {
if((i/10 + i%10 + j/10 + j%10) <= k) sum++;
}
}
return sum;
}
}
对比方法二发现,好家伙本来就是错的,还是太天真了啊,看评论说是有的部分没法绕过去导致的。
class Solution {
boolean[][] visited;
public int movingCount(int m, int n, int k) {
// boolean[][] visited = new boolean[m][n];//重新定义一个存放是否可达的矩阵
visited = new boolean[m][n]; // 上面那句在函数中不能用,但是下面这句可以,因为在开头加了申明
return dfs(0, 0, m, n, k); // 直接 进入函数
}
private int dfs(int i, int j, int m, int n, int k) {
//如果搜索不带-1,那么这里判断也没有必要加<0
if(i >= m || j >= n | (i/10 + i%10 + j/10 + j%10) > k || visited[i][j] ) return 0; // 如果索引超出边界,坐标和大于k,已经来过,都返回0
//取坐标值之和的方法很巧妙,值得学习
visited[i][j] = true;
return dfs(i+1, j, m, n, k) + dfs(i, j+1, m, n, k) + 1; //不加1就白找了
}
}
这种方法将布尔矩阵定义在了大函数里,就使得递归看起来更加简单了,而且因为起点在题目中已经定义过是(0,0),那么直接往右下方搜索就好了,比最初的版本要更优。
执行结果:通过
显示详情
执行用时:1 ms, 在所有 Java 提交中击败了84.99%的用户
内存消耗:35.2 MB, 在所有 Java 提交中击败了88.27%的用户
学习他人:
方法一:
深度优先搜索,后来发现虽然写的bfs,但是仍然是深度优先搜索,先求的r+1。
Bk8rUn4syP 2020-02-26
简洁清晰版Java代码
执行用时 : 1 ms , 在所有 Java 提交中击败了 98.63% 的用户
内存消耗 : 36.5 MB , 在所有 Java 提交中击败了 100.00% 的用户
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(0, 0, m, n, k, visited);
}
private int dfs(int i, int j, int m, int n, int k, boolean visited[][]) {
if (i < 0 || i >= m || j < 0 || j >= n || (i/10 + i%10 + j/10 + j%10) > k || visited[i][j]) {
return 0;
}
visited[i][j] = true;
return dfs(i + 1, j, m, n, k, visited) + dfs(i - 1, j, m, n, k, visited) +
dfs(i, j + 1, m, n, k, visited) + dfs(i, j - 1, m, n, k, visited) + 1;
}
}
方法二 错误解法
ToddL2 2020-04-08 暴力,但是不正确,有问题
不止我一个人这么认为的吧
class Solution {
public int movingCount(int m, int n, int k) {
int ans = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (k - (i / 10 + i % 10) - (j / 10 + j % 10) >= 0)
ans++;
}
}
return ans;
}
}
方法三:
比方法一在函数中少用了一个布尔矩阵,但是依然实现了功能
Sweetiee 🍬L6 2020-04-08 🙋中午弱弱打卡。。 dfs
class Solution {
boolean[][] visited;
public int movingCount(int m, int n, int k) {
visited = new boolean[m][n];
return dfs(0, 0, m, n, k);
}
private int dfs(int x, int y, int m, int n, int k) {
if (x >= m || y >= n || visited[x][y]
|| (x % 10 + x / 10 + y % 10 + y / 10) > k) {
return 0;
}
visited[x][y] = true;
return 1 + dfs(x + 1, y, m, n, k) + dfs(x, y + 1, m, n, k);
}
}
方法四
Daniel (编辑过)2020-04-08 分析很详细
不是算法,算个笨方法吧。
执行用时 : 1 ms, 在所有 Java 提交中击败了90.45%的用户 内存消耗 : 36.7 MB, 在所有 Java 提交中击败了100.00%的用户
个人的想法不是以小机器人的运动轨迹为主,而是判断最大方格中能满足条件的坐标以及它们之间的接触点。 先不管m和n的值为多大,在最大方格上进行模拟,可以看出: 当k逐渐增大时,可到达的格子范围是以(0,0)为顶点的等腰直角三角形,例如:
当k = 8;
0 1 2 3 4 5 6 7 8 9 10
0 可 可 可 可 可 可 可 可 可 不 可
1 可 可 可 可 可 可 可 可 不 不 可
2 可 可 可 可 可 可 可 不 不 不 可
3 可 可 可 可 可 可 不 不 不 不 可
4 可 可 可 可 可 不 不 不 不 不 可
5 可 可 可 可 不 不 不 不 不 不 可
6 可 可 可 不 不 不 不 不 不 不 可 (可为可到达的,不为不可到达的)
7 可 可 不 不 不 不 不 不 不 不 可
8 可 不 不 不 不 不 不 不 不 不 不
9 不 不 不 不 不 不 不 不 不 不 不
10可 可 可 可 可 可 可 可 可 可 不
由此可见,当k < 9 时满足条件的坐标只在10 * 10 的格子以内,是由于小机器人无法走在“不”上面,所以接触不到其他满足条件的格子,所以k < 9是一个临界点,这个界就是 10 * 10 的等腰直角三角形。 当k = 9时,小机器人就可以通过(0,9)和(9,0)向旁边 10 * 10 的格子探索,探索范围则是 20 * 20 的等腰直角三角形; 当k = 10时,小机器人可以通过(0,19)和(19,0)向旁边 10 * 10 的格子探索,也可以通过(10,9)和(9,10)向旁边 10 * 10 的格子探索,探索范围则是 30 * 30 的等腰直角三角形;以此类推,范围是边长为(k - 7)* 10的等腰直角三角形。 此外,既然范围是一个三角形,那么在循环中就可以简化,也就是 i + j 小于(k - 7)* 10,也就是三角形斜边上的点作为每一行的边缘点。 代码如下:
public int movingCount(int m, int n, int k) {
int count = 0;
int round = 10;
if (k >= 9) {
round = (k - 7) * 10;
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n && i + j < round; j++) {
int sumI = i / 100 + i / 10 + i % 10;
int sumJ = j / 100 + j / 10 + j % 10;
if (sumI + sumJ <= k) {
count++;
}
}
}
return count;
}
方法五
还有参考书上方法的同学,看起来就比较繁琐了
2021-02-04
JAVA 1ms
class Solution {
int counter = 0;
public int movingCount(int m, int n, int k) {
boolean [][] visited = new boolean[m][n];
movingTo(0,0,visited,k);
return counter;
}
private void movingTo(int m, int n, boolean[][] visited, int k) {
if (m<0||m>=visited.length||n<0||n>=visited[0].length){
return;
}
if (visited[m][n]){
return;
}
if(getNum(m)+getNum(n)>k){
return;
}
visited[m][n]=true;
counter++;
movingTo(m+1,n,visited,k);
movingTo(m-1,n,visited,k);
movingTo(m,n+1,visited,k);
movingTo(m,n-1,visited,k);
}
int getNum(int n){
int result =0;
while (n>0){
result += n%10;
n/=10;
}
return result;
}
}
方法六
官方精选,广度优先搜索,没有深度看起来那么直观了。
Java/C++ 代码中 visited
为辅助矩阵,Python 中为 Set 。
作者:jyd
链接:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/mian-shi-ti-13-ji-qi-ren-de-yun-dong-fan-wei-dfs-b/
来源:力扣(LeetCode)
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;
}
}
总结
以上就是本题的内容和学习过程了,本题已经相当具有难度了,还是多学多练,争取早日自己能够摸索出这类题的答案,培养自己的解题逻辑和习惯。
欢迎讨论,共同进步。