剑指Offer 13 14

295 阅读3分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

剑指 Offer 13. 机器人的运动范围

题目

地上有一个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

方法一

bfs:从{0,0}开始宽搜,若搜到的点没有被遍历过,且符合各数位值相加<=k,则加入队列。用dfs深搜也可以,不过dfs存在爆栈的隐患

class Solution {
    Queue<int[]> q;
    boolean[][] st;
    int[] dx = {0,0,1,-1}, dy = {1,-1,0,0};
​
    public int movingCount(int m, int n, int k) {
        q = new LinkedList<int[]>();
        st = new boolean[m][n];
        return bfs(m, n, k);
    }
​
    public int bfs(int m, int n, int k) {
        int res = 0;
        q.add(new int[] {0, 0});
        st[0][0] = true;
​
        while (q.size() > 0) {
            int[] t = q.poll();
            int a = t[0], b = t[1];
            res ++;
            for (int i = 0; i < 4; i ++ ) {
                int x = a + dx[i], y = b + dy[i];
                if (x < 0 || x >= m || y < 0 || y >= n) continue;
                if (!st[x][y]) {
                    int cnt = 0;
                    int t1 = x, t2 = y;
                    for (int j = 0; j < 3; j ++ ) {
                        cnt += (t1 % 10) + (t2 % 10);
                        t1 /= 10; t2 /= 10;
                    }
                    if (cnt <= k) {
                        q.add(new int[] {x, y});
                    }
                    st[x][y] = true;
                }
            }
        }
        return res;
    }
}

时间复杂度: O(n)

空间复杂度: O(n)

剑指 Offer 14- I. 剪绳子

题目

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]k[1]...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:

  • 2 <= n <= 58

方法一

数学方法:证明一可以参照这里,这里证明二参照y总的小学生证明法

假设把n拆分成了a1a2...*an是最优解,

  • a中一定没有大于等于5的数,反证法:如果a中有,则该数可以拆分为(a - 3) + 3,而3(a - 3) > a,当a等于4.5时
  • a中一定没有1,因为拆分成1 + (n - 1),乘积还没有不拆的大
  • 综上所有的a一定在2、3、4中
  • 而4又可以拆分为2 + 2,拆分前后是相等关系,所以最优解一定可以被若干个2、3所表示
  • 且2最多为2个,假设有3个2,2 + 2 + 2 = 6 = 3 + 3,没有拆分为2个3大
  • 因此最优解一定是可以被若干个3和最多2个2所表示
class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) return 1 * (n - 1);
        int res = 1;
        res *= Math.pow(3, n / 3);
        n %= 3;
        if (n == 1) res = res / 3 * 4; // n=1,说明最后一个3可以和这个1组成2、2,所以把最后一个3除了,再乘个4
        else if (n == 2) res *= 2; 
        return res;
    }
}

时间复杂度: O(1)

空间复杂度: O(1)

注意: 题意为至少分成2段,所以当n <= 3时,需要特判一下

方法二

动态规划:

  • 状态定义f[i]表示将i拆分后的最大乘积
  • 状态计算,以f[i]结尾一段来考虑,i被分成了ji-ji-j为靠尾一段,当i-j固定时,j可以拆,也可以不拆
  • j不拆乘积就是i*(i - j)j拆的话,乘积为(i - j) * f[j]
  • 因此状态转移方程为 f[i] = max(f[i], (i - j) * max(j, f[j]))
class Solution {
    public int cuttingRope(int n) {
        int[] f = new int[n + 1];
        
        f[1] = f[2] = 1;
        for (int i = 3; i <= n; i ++ ) 
            for (int j = 1; j < i; j ++ ) 
                f[i] = Math.max(f[i], (i - j) * Math.max(f[j], j));
        
        return f[n];
    }
}

时间复杂度: O(n*n)

空间复杂度: O(n)