【剑指Offer】个人学习笔记_13_机器人的运动范围

刷题日期: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;
    }

方法五

还有参考书上方法的同学,看起来就比较繁琐了

1gwlGIAOL1

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;
    }
}

总结

以上就是本题的内容和学习过程了,本题已经相当具有难度了,还是多学多练,争取早日自己能够摸索出这类题的答案,培养自己的解题逻辑和习惯。

欢迎讨论,共同进步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值