👋 欢迎阅读

🎯 欢迎来到「坏了的计算器」题解之旅! 本文将带你从"通过双倍和减一操作将数字变为目标值"这一逆向思维问题出发,深入理解贪心算法 的巧妙转化,并掌握如何从 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 可以理解为"补齐奇偶性"以便整除,而用除法代替多次减法能大幅减少操作次数。现在,让我们一起反向操作,从目标倒退到起点,找出那个最少的操作步数吧! 🔢🔄
一、题目

二、做题思路
1. 问题分析(前置分析)
给定 startValue 和 target,正向操作只有两种:乘 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. 实现细节(边界防护)
-
使用 整型变量 存储
target和startValue,无需考虑浮点数。 -
循环条件为
target != startValue,确保精确相等时退出。 -
判断
target % 2 == 0来决定是否可除以 2;若为奇数,则执行target++。 -
当
target < startValue时,直接循环target++直到相等(此时除以 2 无意义)。 -
操作次数
ret在每次操作后自增。
5. 返回值(目标映射)
返回 ret,即 从 startValue 到 target 所需的最小操作数。
四、代码
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 < 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可行的必要前序操作**。 |
+----------------------------------------------------+
-
正向操作从
s到t,逆向则从t到s。 -
每次逆向操作对应正向操作的一次逆,操作次数相同。
-
目标 :用最少的逆向操作将
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,你还能用同样的逆向思路求解吗?贪心策略需要如何调整?
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者,获取更多题解
💬 留言交流你的疑问或优化思路
祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨