LeetCode 991: 坏了的计算器(贪心算法) —— 题解

👋 欢迎阅读

🎯 欢迎来到「坏了的计算器」题解之旅! 本文将带你从"通过双倍和减一操作将数字变为目标值"这一逆向思维问题出发,深入理解贪心算法 的巧妙转化,并掌握如何从 target 反向推导而非从 startValue 正向模拟,从而高效求出最小操作数。

在开始之前,建议你先:

  • 了解题目背景 :这是 LeetCode 991 题,给定初始值 startValue 和目标值 target,允许两种操作:乘 2 (双倍)和减 1 (递减),求将 startValue 变为 target 所需的最少操作数 。如果正向模拟,分支较多;但如果反向思考 ------从 target 变回 startValue,操作变为除以 2 (当 target 为偶数时)和加 1,问题就变得清晰而简洁。

  • 明确学习目标 :掌握反向贪心策略 ------当 target > startValue 时,优先使用除以 2 (因为这是正向中"乘 2"的逆操作,能最快缩小差距),若 target 为奇数则先加 1 使其变为偶数再除以 2;当 target <= startValue 时,只能用加 1 (对应正向的减 1)逐步补齐。理解为什么这种"尽量用除法"的策略能保证最少操作数(贪心选择性质:除法比多次减法更高效),并熟练处理边界情况。

  • 准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如 startValue = 3, target = 10 输出 3)。

本文将从问题转化、反向贪心策略设计(优先除法)、正确性直觉 到代码实现,层层递进。即使你对贪心算法还不熟悉,我们也会从"从目标往回走,能除就除,不能除再加 1 "这一直觉出发,让你轻松抓住核心思想 ------正向的减 1 会让数字变小,反向的加 1 可以理解为"补齐奇偶性"以便整除,而用除法代替多次减法能大幅减少操作次数。现在,让我们一起反向操作,从目标倒退到起点,找出那个最少的操作步数吧! 🔢🔄


一、题目

991. 坏了的计算器 - 力扣(LeetCode)

二、做题思路

1. 问题分析(前置分析)

给定 startValuetarget,正向操作只有两种:乘 2(Double)减 1(Decrement) 。要求从 startValue 到达 target最少操作数

逆向思考 :从 target 出发,逆向操作对应为 除以 2 (正向乘 2 的逆)和 加 1 (正向减 1 的逆)。目标是将 target 变为 startValue,每次逆向操作视为一次正向操作,累积次数即为答案。

核心贪心思想 :在逆向过程中,尽量使用"除以 2"来快速缩小 target ,因为一次除 2 比多次加 1 更高效。但 target 为奇数时不能直接除以 2 (会得到小数,题目不允许),此时只能通过 加 1 使其变为偶数,再除以 2。


2. 贪心策略(核心决策规则)

  • 初始化计数器 ret = 0

  • 循环直到 target == startValue

    • target < startValue ,则逆向只能通过 不断加 1 来增加 target,直到相等。

    • target > startValue

      • target 为偶数 ,则 优先执行除以 2(一次操作大幅减小)。

      • target 为奇数 ,则 必须执行加 1(因为除以 2 会产生小数,不可行),使其变为偶数后再考虑除以 2。

  • 每执行一次操作(加 1 或除以 2),ret++

  • 最终返回 ret


3. 正确性说明(简单版本)

逆向操作中,除以 2 比加 1 的"效率"高得多 :一次除 2 能将数字减半,而加 1 每次只增加 1。因此,在可行的前提下尽可能多地使用除以 2 是贪心最优策略

target 为奇数时,不能除以 2 ,因为会导致非整数结果,不符合整数运算规则。此时唯一的逆向操作是 加 1 ,使其变为偶数后,才可继续使用除以 2。这种策略保证了在每步都选择当前最快的缩小方式,且不会错过最优解(因为若奇数时选择不加 1 而尝试其他操作,没有其他合法操作可选)。

target 已经小于 startValue,则除以 2 只会更小,无法接近目标,此时只能通过加 1 逐步增大,这也是唯一可行路径。因此贪心策略正确。


4. 实现细节(边界防护)

  • 使用 整型变量 存储 targetstartValue,无需考虑浮点数。

  • 循环条件为 target != startValue,确保精确相等时退出。

  • 判断 target % 2 == 0 来决定是否可除以 2;若为奇数,则执行 target++

  • target < startValue 时,直接循环 target++ 直到相等(此时除以 2 无意义)。

  • 操作次数 ret 在每次操作后自增。


5. 返回值(目标映射)

返回 ret,即 startValuetarget 所需的最小操作数

四、代码

cpp 复制代码
class Solution 
{
public:
    int brokenCalc(int startValue, int target) 
    {
        // 逆向思维:从 target 出发,通过逆向操作(对应正向操作的逆操作)变成 startValue,
        // 每次操作等价于正向的:乘以2 或 减1。
        // 逆向操作:
        //   - 正向"乘以2"的逆操作是"除以2"(仅当 target 为偶数时可行)
        //   - 正向"减1"的逆操作是"加1"
        // 贪心策略:在逆向过程中,优先使用"除以2"来快速缩小 target,
        // 只有当 target 为奇数或 target < startValue 时才使用"加1"。
    int ret = 0;   // 记录操作次数

    // 循环直到 target 变为 startValue
    while (true) 
    {
        // 如果已经相等,返回累计操作次数
        if (target == startValue) 
        {
            return ret;
        }

        // 情况1:target 小于 startValue(逆向时只能通过加1来增大,因为除以2只会更小)
        // 此时不断加1,直到 target == startValue
        else if (target &lt; startValue) 
        {
            target++;
        }
        // 情况2:target 大于 startValue
        else 
        {
            // 如果 target 是偶数,优先使用除以2(一次操作可大幅减小,更快接近 startValue)
            if (target % 2 == 0) 
            {
                target = target / 2;
            }
            // 如果 target 是奇数,则只能先加1使其变为偶数,然后再除以2(贪心选择)
            else 
            {
                target++;
            }
        }

        // 每执行一次操作(无论哪种),操作次数加1
        ret++;
    }

    // 理论上循环内会返回,不会执行到这里
    return -1;
}
};

五、流程图

六、正确性说明(详细版)

步骤 1:符号与问题建模
复制代码
+----------------------------------------------------+
|  正向操作:从 startValue 到 target                 |
|  ① 乘 2(Double)                                 |
|  ② 减 1(Decrement)                              |
|  目标:最少操作次数                                |
+----------------------------------------------------+
                    |
                    v
+----------------------------------------------------+
|  逆向操作:从 target 回到 startValue               |
|  ① 除以 2(仅当偶数)对应正向乘2的逆              |
|  ② 加 1 对应正向减1的逆                          |
|  贪心策略:在 target > startValue 时,             |
|  若 target 为偶数,优先除以2;                    |
|  若 target 为奇数,则加1使其变成偶数;             |
|  若 target < startValue,则只能不断加1。           |
|  贪心实质:**每次除以2能最大幅度缩小差距**,       |
|  奇数时加1是**使除以2可行的必要前序操作**。        |
+----------------------------------------------------+
  • 正向操作从 st,逆向则从 ts

  • 每次逆向操作对应正向操作的一次逆,操作次数相同

  • 目标 :用最少的逆向操作将 t 变为 s


步骤 2:关键性质 ------ 贪心选择的必要性(反证法)
复制代码
+----------------------------------------------------+
|  性质 1:当 t > s 且 t 为偶数时,最优解必须先用   |
|  除以2(而不是加1)。                              |
|  反证:若先加1再除以2,则两次操作后值为 (t+1)/2, |
|  而直接除以2后值为 t/2,再根据需要操作。          |
|  显然 (t+1)/2 > t/2,直接除以2使数值更小,        |
|  后续所需操作不会更多,且总操作次数更少。          |
|  具体:假设先加1再除2,操作2次;直接除2只需1次,  |
|  且剩余目标更小,因此前者不可能更优。              |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  性质 2:当 t > s 且 t 为奇数时,必须至少执行一次 |
|  加1操作才能使 t 变为偶数,从而可以除以2。         |
|  由于除以2是唯一能快速缩小数值的方式,            |
|  故在奇数情况下,贪心选择加1是**必要的**。         |
|  反证:若不加1而多次加1或直接加其他数,            |
|  则无法除以2,缩小速度慢,操作数必然更大。          |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  性质 3:当 t < s 时,正向操作中无法通过乘2缩小,  |
|  逆向只能通过加1逐步增加,直到相等。               |
|  此时没有其他选择,**贪心决策唯一且最优**。         |
+----------------------------------------------------+

详细论证

  • 性质1(偶数优先除2) :设 t 为偶数且 t > s。若最优解的第一步不是除以2,而是加1,那么它必然要经历 t → t+1 → (t+1)/2(至少两步)才能除以2。而直接除以2只需一步,得到 t/2。因为 (t+1)/2 > t/2,直接除以2后的数更小,距离目标 s 更近,且已经节省了一步操作,因此任何不先除以2的方案都不可能比贪心方案更优

  • 性质2(奇数必须加1) :当 t 为奇数时,除以2不可行,唯一的可执行逆向操作是加1。加1后变成偶数,才能继续除以2。因此加1是强制性的前序操作,贪心选择加1是唯一合理的决策。

  • 性质3(小于目标时只能加1) :当 t < s 时,除以2会使 t 更小,远离目标,因此不能使用。唯一可行的操作是加1,不断递增直到等于 s。此时无选择余地,贪心自然最优。


步骤 3:归纳证明 ------ 贪心逐步构造全局最优
复制代码
+----------------------------------------------------+
|  初始:当前值为 t,步数 ret=0。                    |
|  归纳假设:每次贪心操作后,剩余问题与原始问题      |
|  同构,且当前值到目标 s 的最优步数 =              |
|  1 + 剩余问题的最优步数。                          |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  每次操作:                                        |
|  - 若 t == s,返回 ret。                          |
|  - 若 t < s,只能加1,执行。                     |
|  - 若 t > s 且偶数,由性质1,必须除以2,执行。   |
|  - 若 t > s 且奇数,由性质2,必须加1,执行。     |
|  每次操作后,问题规模减小(t 更接近 s),          |
|  且贪心选择是必要的,归纳保持。                    |
+----------------------------------------------------+
                     |
                     v
+----------------------------------------------------+
|  最终 t == s 时,ret 即为最小操作次数。            |
|  因为每一步选择都是强制或最优的,无其他选择可      |
|  减少总步数。                                      |
+----------------------------------------------------+

详细论证

  • 归纳基础 :当 t == s 时,无需操作,ret=0 显然最小。

  • 归纳步骤 :假设当前 t 尚未等于 s,考虑所有可能的逆向操作:

    • t < s,任何操作都不能是除以2(会远离),只能加1。因此这一步是唯一的,贪心执行。

    • t > s 且为偶数,由性质1,任何最优解的第一步都必须是除以2。贪心执行后,剩余问题变为 (t/2, s),步数加1。

    • t > s 且为奇数,由性质2,任何最优解的第一步都必须是加1(因为除以2不可行)。贪心执行后,剩余问题变为 (t+1, s),步数加1。

  • 由于每一步的选择都被证明是强制或最优的,且操作后 t 单调减小(或增大)向 s 靠拢,最终必然停止。因此贪心算法得到的总步数等于理论最小步数。

🎯 闭幕

🎉 恭喜你完成了「坏了的计算器」问题的学习!

为了巩固知识并进一步拓展,建议你:

🚀 动手实践

在 LeetCode 上提交代码,尝试不同的测试用例。

💡 深入思考

  • 本题采用 逆向思维 + 贪心策略 :从 target 出发,逆向操作为 除以 2 (偶数时)和 加 1 ,直到变成 startValue为什么逆向贪心优先用"除以 2"能保证操作次数最少? 如果正向从 startValue 出发用"乘 2"和"减 1",贪心是否还成立?

  • target 是奇数时,代码选择先 加 1 使其变为偶数,再除以 2。为什么不能先减 1 再除以 2? 试比较两种做法对最终操作次数的影响,并用例子验证(如 startValue=3, target=10)。

  • 如果 target < startValue,代码直接不断加 1 直到相等。为什么此时不能使用"除以 2"来缩小? 如果强行除以 2,会有什么问题?

📚 延伸挑战

  • 将操作改为 乘 3减 1,你还能用同样的逆向思路求解吗?贪心策略需要如何调整?

如果你觉得本文对你有所帮助,欢迎:

👍 点赞 / 收藏

👤 关注作者,获取更多题解

💬 留言交流你的疑问或优化思路

祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨

相关推荐
旖旎夜光4 小时前
LeetCode 553:最优除法(贪心算法) —— 题解
数据结构·c++·算法·leetcode·贪心算法
雾喔4 小时前
算法练习7
java·数据结构·算法
可编程芯片开发5 小时前
基于模糊PID控制的六步逆变器供电无刷直流电机调速simulink仿真
算法
imperialeast5 小时前
WinAXP音乐播放器8月4日更新
windows·算法·ui
ZhouDevin6 小时前
算法论文/模型微调1——CNN架构做lora微调训练
人工智能·深度学习·算法·架构·cnn
小月土星6 小时前
LeetCode 21. 合并两个有序链表 超详细题解(虚拟头结点 + 逐行代码拆解)
算法
oier_Asad.Chen6 小时前
【OI学习笔记】Floyed-Warshall算法解决传递闭包问题
c++·笔记·学习·算法·图论·最短路·传递闭包
国科安芯6 小时前
卫星星务计算机数据管理中SEU容错机制的技术实现研究——以AS32S601存储器ECC架构为例
网络·单片机·算法·fpga开发·架构·云计算·risc-v
孙启超6 小时前
【AI应用开发】Agent 无限 loop(反复调用同一个工具)如何规避?
人工智能·python·算法·llm·agent·loop·ai应用开发