在问 · 算法题解

397. 整数替换

中等动态规划记忆化搜索力扣原题 ↗

题目描述

输入一个正整数 n。每一步可以这样变换:当 n 是偶数时,把它换成 n / 2;当 n 是奇数时,可以换成 n + 1,也可以换成 n - 1。

返回把 n 变成 1 最少需要多少步。

示例 1
输入:n = 8
输出:3

解释:8 -> 4 -> 2 -> 1。

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

解释:7 -> 8 -> 4 -> 2 -> 1 或 7 -> 6 -> 3 -> 2 -> 1,都需要 4 步。

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

解释:4 -> 2 -> 1。

数据范围

  • 1 <= n <= 2^31 - 1

解题思路:记忆化搜索

设 f(x) 为把 x 变成 1 的最少步数,f(1) = 0。偶数只有一种走法:f(x) = f(x/2) + 1;奇数要比较两条路:f(x) = min(f(x-1), f(x+1)) + 1。x-1 与 x+1 都是偶数,下一步立刻减半,所以递归深度只有 O(log n)。不同的子问题会反复出现(例如 (x-1)/2 和 (x+1)/2 的后代互相重叠),用记忆化缓存避免重复计算。

为什么正确

缓存里的每个 f(x) 都已经是 x 变成 1 的最少步数;任何一次调用只在所有子调用得到最优值后才写入缓存。

复杂度分析

时间 O(log n):每一层最多出现两个相邻的数,状态数约 2 log n。空间 O(log n),用于缓存和递归栈。

易错点

奇数时直接 n + 1 可能超出 32 位整数范围(n = 2^31 - 1),C++/Java 要用 long。另外 3 是特例:3 -> 2 -> 1 比 3 -> 4 -> 2 -> 1 少一步,贪心规则要单独处理它。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def integerReplacement(self, n: int) -> int:
        @functools.lru_cache(None)
        def f(x):
            if x == 1:
                return 0
            if x % 2 == 0:
                return f(x // 2) + 1
            return min(f(x - 1), f(x + 1)) + 1
        return f(n)

载入编辑器,自己跑一遍 →

其他解法

记忆化搜索把两种选择都试一遍再取较小值;贪心直接根据二进制末两位做出最优选择,省掉了搜索和缓存。

解法 2:贪心(看二进制末两位) 时间 O(log n),空间 O(1)

n 为偶数时直接除以 2。n 为奇数时看二进制末两位:末两位是 01 就减 1,末两位是 11 就加 1,这样下一步能连续除以 2 的次数更多。唯一例外是 n = 3,减 1 更优。循环到 n = 1 为止,统计步数。

不需要递归和哈希表,空间 O(1)。正确性依赖对末两位的分析,不如记忆化搜索直观。加 1 可能超出 32 位整数,需要用 64 位整数。

class Solution:
    def integerReplacement(self, n):
        ans = 0
        while n != 1:
            if n % 2 == 0:
                n //= 2
            elif n == 3 or n % 4 == 1:
                n -= 1
            else:
                n += 1
            ans += 1
        return ans

动态规划 专题的其他题目

查看 动态规划 全部题目 →