坏了的计算器

标签: 贪心 数学

难度: Medium

在显示着数字 startValue 的坏计算器上,我们可以执行以下两种操作:

  • 双倍(Double):将显示屏上的数字乘 2;
  • 递减(Decrement):将显示屏上的数字减 1

给定两个整数 startValue 和 target 。返回显示数字 target 所需的最小操作数。

示例 1:

输入:startValue = 2, target = 3
输出:2
解释:先进行双倍运算,然后再进行递减运算 {2 -> 4 -> 3}.

示例 2:

输入:startValue = 5, target = 8
输出:2
解释:先递减,再双倍 {5 -> 4 -> 8}.

示例 3:

输入:startValue = 3, target = 10
输出:3
解释:先双倍,然后递减,再双倍 {3 -> 6 -> 5 -> 10}.

提示:

  • 1 <= startValue, target <= 109

Submission

运行时间: 24 ms

内存: 16.2 MB

class Solution:
    def brokenCalc(self, startValue: int, target: int) -> int:
        operations = 0
        
        # 逆向操作直到 target 达到或超过 startValue
        while target > startValue:
            if target % 2 == 0:  # 偶数,逆向双倍操作
                target //= 2
            else:  # 奇数且 target < startValue,逆向递减操作
                target += 1
            operations += 1
        
        # 加上 startValue 到 target 之间的差值(递减操作次数)
        operations += startValue - target
        
        return operations

Explain

该题解采用了逆向思维的策略。从 target 开始,逆向操作直到达到或小于 startValue。如果 target 是偶数,则进行逆向的双倍操作(实际上是除以 2),如果是奇数,则通过逆向的递减操作(实际上是加 1)使其变为偶数。这种逆向操作持续进行,直至 target 小于或等于 startValue。一旦 target 小于 startValue,剩余的操作数即为两者之差,因为每一次递减操作相当于直接从 startValue 减去 1 直到等于 target。这种逆向策略利用了除法迅速减小数值的特性,通常比正向操作更快达到目标。

时间复杂度: O(log(target))

空间复杂度: O(1)

class Solution:
    def brokenCalc(self, startValue: int, target: int) -> int:
        operations = 0
        
        # 逆向思考:尝试将 target 逆向操作至 startValue
        while target > startValue:
            if target % 2 == 0:  # 如果 target 是偶数,则逆向双倍操作,即除以 2
                target //= 2
            else:  # 如果 target 是奇数,则逆向递减操作,即加 1
                target += 1
            operations += 1
        
        # target 小于 startValue 后,直接通过递减操作将 startValue 调整到 target,操作数为它们的差
        operations += startValue - target
        
        return operations

Explore

优先将奇数变为偶数再除以2的原因是,这样的操作可以更快地减少target的值。直接对奇数减1直到与startValue相等的方法虽然直观,但在数值较大时操作次数会显著增加。例如,如果target是奇数,通过加1使其变偶后再除以2,可以在一步内将数值减半,显著提高效率。相反,如果只是简单地逐一减1,则每次只减少1,需要更多的操作来达到相同的效果,尤其是当target远大于startValue时。

当target经过除以2操作后变得小于startValue时,剩余的操作步骤应该是对startValue直接进行递减操作,直到达到target。这是因为此时target已经小于startValue,直接每次递减1(实际上在代码中是增加操作次数并减少startValue)是最直接且操作数最少的方法。这种方式确保了操作数最小化,因为任何其他操作比如倍增都可能导致startValue超过target,需要额外的调整步骤。

逆向操作策略并不总是优于正向操作,但在很多情况下更加高效。逆向操作通过除以2的方式迅速减小数值,尤其适用于target远大于startValue的情况。例如,如果startValue为1而target为100,正向操作需要多次倍增和增加操作,而逆向操作只需几次除以2和加1操作即可到达1。然而,在某些情况下,如target与startValue数值相近且都较小,正向操作可能更为直接和少步骤。

在将target调整到小于startValue的情况中,通常考虑的是直接递减操作,因为这是最直接且确保操作次数最少的方式。考虑使用倍增操作可能导致过度调整,即startValue可能增加至超过所需的target,反而增加了调整回较小数值的操作。因此,在这种情况下通常不采用倍增操作,以避免可能的过度调整和增加不必要的操作步骤。