LeetCode 991. 坏了的计算器
题目描述


题意分析
从正整数
startValue出发,每次只能乘 2 或减 1,求到达target的最少操作数。把操作顺序倒过来,就等价于从target回到startValue:乘 2 的逆操作是偶数除以 2,减 1 的逆操作是加 1,路径长度不变。
解法:逆向贪心
核心思路
[!blue]
设当前逆向数值为
target。当它大于起点时,最终必须让数值下降,不能一直加 1。若它是奇数,除以 2 不合法,因此第一步只能加 1,将它变成偶数。若它是偶数,立即除以 2 不会错过更优解。任何先增加再除以 2 的方案,在第一次除法之前必须增加偶数次,设为
2r次,之后到达(target + 2r) / 2。改成先除以 2、再增加r次,仍到达同一数值,却把这部分操作从2r + 1次降为r + 1次。所以可以把除法提前,选择立即减半。当
target <= startValue时,已经需要把数值向上补到起点。每次加 1 最多补一单位,而除以 2 只会让差距更大,因此至少需要startValue - target次加法,直接连续加 1 恰好达到这个下界。循环只处理目标较大的部分,记录已执行的逆操作次数
ops;结束后一次加上剩余差值,即为原问题的最少操作数。
解题步骤
- 初始化
ops = 0,当target > startValue时继续循环。- 当前目标为奇数则加 1,为偶数则除以 2;每执行一个逆操作,
ops加 1。- 目标降到不大于起点后停止,返回
ops + startValue - target。- 若起初目标已经不大于起点,循环不会执行;两者相等时答案为 0。
代码实现
class Solution {
public int brokenCalc(int startValue, int target) {
int ops = 0;
while (target > startValue) {
// 奇数不能整除,逆向必须先加一。
if ((target & 1) == 1) {
target++;
} else {
target >>= 1;
}
ops++;
}
// 目标不大于起点后,直接补足剩余差值。
return ops + (startValue - target);
}
}
func brokenCalc(startValue int, target int) int {
ops := 0
for target > startValue {
// 奇数不能整除,逆向必须先加一。
if target%2 == 1 {
target++
} else {
target /= 2
}
ops++
}
// 目标不大于起点后,直接补足剩余差值。
return ops + (startValue - target)
}
复杂度分析
- 时间复杂度:$O(\log(target+1))$ 上界。循环中偶数一步减半,奇数至多两步变为原值的一半向上取整;目标起初不大于起点时为 $O(1)$。
- 空间复杂度:$O(1)$。只维护当前目标和操作次数。
关键点总结
[!green]
- 逆向操作与正向操作逐一对应,最短路径长度保持不变。
- 奇数加 1 是唯一合法的第一步;偶数立即除以 2 由操作交换证明最优。
- 目标不大于起点后,直接补差值就是最优解,不再继续套用奇偶分支。
易错点总结
[!yellow]
- 奇数加 1 后再除以 2 是两次操作,不能只记一次。
- 奇数直接使用整数除法不对应合法逆操作,会丢掉必要的加 1。
- 结束条件是目标不大于起点,而非必须完全相等;低于起点时要补上剩余差值。
- 正向只要能乘 2 就优先乘 2 缺少上述最优性保证,不能直接照搬逆向规则。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 397. 整数替换 | 中等 | 同样反向处理倍增可减少分支,本题逆操作为除2或加1,原题整数替换还允许两种奇数调整。 |
| 2139. 得到目标值的最少行动次数 | 中等 | 同样从目标反推并尽量利用除2,原题额外限制倍增次数。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!