leetcode-整数替换

163 阅读3分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

这段时间比较放松,节日的氛围也会让人变得慵懒。不过新年之初,以早为贵,除了每天都尽量早起之外,也不能忘记自己立下的flg,继续leetcode做题。

题目

给定一个正整数 n ,你可以做如下操作:
如果 n 是偶数,则用 n / 2替换 n 。
如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
返回 n 变为 1 所需的 最小替换次数 。

示例 1:
输入:n = 8
输出:3
解释:8 -> 4 -> 2 -> 1

示例 2:
输入:n = 7
输出:4
解释:7 -> 8 -> 4 -> 2 -> 1 或 7 -> 6 -> 3 -> 2 -> 1

示例 3:
输入:n = 4
输出:2

思路

一开始使用搜索的方式做了,因为根据题意,偶数只能除以2,不用过多考虑;奇数可以选择+1或者-1,需要选择让接下来步数比较少的那个。可以用一个Map来存储,键是n,值是n还原到1最小的次数,初始,我们有键值对(1,0)。接下来,我们可以用如下伪代码思路去递归搜索:

int f(n) {
    if(map.containsKey(n)) {
        return map.get(n);
    }
    if(n % 2 == 0) {
        ans = 1 + f(n/2);
        map.put(n, ans);
        return ans;
    } else {
        ans = 1 + min(f(n+1), f(n-1));
        map.put(n, ans);
        return ans;
    }
}

因为n+1肯能会超过int,所以这里可以进行一些转化,奇数+1或者-1后一定是偶数,而偶数只能除以2,所以,我们可以把这2步合并起来,变成(n+1)/2和(n-1)/2,而n是奇数时,这2个表达式的值其实是 n/2+1 和 n/2,因为n是int,除以2会自动向下取整。这样上面的伪代码可以优化成如下:

int f(n) {
    if(map.containsKey(n)) {
        return map.get(n);
    }
    if(n % 2 == 0) {
        ans = 1 + f(n/2);
        map.put(n, ans);
        return ans;
    } else {
        ans = 2 + min(f(n/2+1), f(n/2));
        map.put(n, ans);
        return ans;
    }
}

目前已经可以解出题目了,不过看了别人的题解,发现用贪心也挺巧妙的,需要一些数学证明过程,这里也写一下。
偶数只能除以2,没啥好说的;重点是奇数,可以分成2类:

  • n % 4 == 1
  • n % 4 == 3 对于类型1,我们的选择总是先-1再除以2,可以使用反证法来证明,假设我们选择先+1再除以2是最优解,因为n % 4 == 1,所以(n+1)/2的结果是奇数,所以接下来又有2种可能:
  • 如果先+1再除以2,那么最终得到的是(n+3)/4,那么我们从(n-1)/2这个中间结果后进行先除以2再+1也会得到(n+3)/4
  • 如果先-1再除以2,那么最终得到的是(n-1)/4,那么我们从(n-1)/2这个中间结果除以2也会得到(n-1)/4 综上所述,我们对于n % 4 == 1的情况,选择先-1再除以2总是最优解,至少是最优解之一; 我们可以用类似的方法去证明n % 4 == 3的情况,选择先+1再除以2总是最优解,除了一个特殊情况,就是n=3,这时先-1再除以2,直接可以得到1。 根据上面的思路可以写出更高效的代码,不过既然我们追求了高效,还有一个点就是,除以2可以用左移1位来实现,不过注意乘除运算优先级是高于加减的,但是位移运算优先级低于加减,所以要注意增加括号。

Java版本代码

class Solution {
    public int integerReplacement(int n) {
        if (n == 1) {
            return 0;
        }
        int ans = 0;
        while (n > 1) {
            if (n % 2 == 0) {
                ans++;
                n = n >> 1;
            } else {
                if (n % 4 == 1) {
                    ans += 3;
                    n = (n - 1) >> 2;
                } else {
                    if (n == 3) {
                        ans += 2;
                        n = 1;
                    } else {
                        ans += 2;
                        n = (n >> 1) + 1;
                    }
                }
            }
        }
        return ans;
    }
}