991 坏了的计算器(正向思维与逆向思维、贪心)

1. 问题描述:

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

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

最初,计算器显示数字 X。返回显示数字 Y 所需的最小操作数。

示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

输入:X = 1024, Y = 1
输出:1023
解释:执行递减运算 1023 次

提示:

  1. 1 <= X <= 10^9
  2. 1 <= Y <= 10^9

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/broken-calculator

2. 思路分析:

① 一开始的时候想到的是搜索的方法,考虑到从起始状态到目标状态的最短步数的特点,所以感觉可以使用广度优先搜索(bfs)或者深度优先搜索(dfs)解决,但是发现太麻烦了,因为主要是乘以2的操作会导致bfs队列中存储太多没有用的数据或者导致dfs一直递归下去往乘以2的方向递归下去而没有终止递归的条件造成了死循环了,所以有的数据是求解不了答案的,而且因为当前数字乘以2之后会导致数字越来越大的,所以使用搜索的方法处理起来是很麻烦的。应该想一下其他的方法解决,X-Y无非有两种操作,当X大于Y的时候肯定是一直执行减1的操作使得两者相等的,主要是解决X小于Y的情况,X小于Y肯定是需要乘2的,一开始的时候可能感觉X-Y的过程中不知道什么时候应该乘以2什么时候减去1,但是感觉Y-X的过程会更容易想一点,所以我们考虑Y-X(其实经历的步骤是一样的),我们可以分为两种情况,第一种是Y为偶数的时候,因为是需要步数最少所以尽可能在X-Y的过程中多执行乘2的操作,所以当Y为偶数的时候上一步肯定是X乘以2的操作,所以对于逆过程应该是Y // 2,当Y为奇数的时候因为是逆过程所以应该加1这样使得下一步操作的时候变为偶数,我们在执行循环的时候进行计数即可

② 除了①中逆向的思维之外,其实还可以使用正向的思维解决,主要是解决X小于Y的情况,我们无非是想要多执行乘2的操作,所以我们先可以一直乘以2直到X大于Y,下面举一个例子比较好理解:

X = 5, Y = 26

5 * 2 * 2 * 2  = 40

可以发现当X 大于Y的时候X = 40,这个时候肯定是执行最多的乘2操作,所以接下来主要解决的是怎么样将减1的操作与乘2的操作进行结合问题,也就是在哪一个乘2的操作前减去1(可能减去多个1),因为现在乘以2之后出现了大于Y的情况,所以我们需要减去多出来的部分,考虑到步数最少,所以尽可能每一次的时候减去尽可能多的数值,所以应该是在第一次乘以2之前需要减去若干个的1,像下面这样:

(5 - x1) * 2 * 2 * 2,所以肯定是在第一次乘以2前减去若干个1的,基于贪心的思想我们需要在第一次乘以2之前减去若干个1,这样减去的2的幂次方是最大的,减去的数值是最大的,相当于是减去了 x1 * 2 * 2 * 2,减去多少个1呢?这个主要是看X 与Y的差值,可以知道X - Y = 14,所以最多有14 // (2 ^ 3) = 1,所以上面的式子变为了,相当于减去了一个8了,这个时候减去的是最多的数值了,假如减取2个1那么相当于减去了16,这样X就小于Y了不满足X逼近Y了,所以应该使用当前X与Y的差值除以当前2的幂次方得到减去的1的数目,然后对剩下来的数值继续执行同样的操作,只是这个时候换为减去2 ^ 2

(5 - 1) * 2 * 2 * 2,接下来也是执行同样的方法,只是这个时候X变为了 14 % (2 ^ 3) = 6,我们的目标是使得最终X = Y,所以剩下的6肯定是在第二次乘以2之前减去若干个的1的,所以应该是:

((5 - 1) * 2 - ? )* 2 * 2,而6最多减去2 ^ 2,即:6// (2 ^ 2) 所以减去的也是一个1,变为了((5 - 1) * 2 - 1 )* 2 * 2,最后剩余2说明乘以第三个2之前应该减去一个1,所以变为了:

(((5 - 1) * 2 - 1 )* 2 - 1)* 2 = 26,所以只需要6个步骤即可完成X-Y的操作

相当于是计算5 * 2 * 2 * 2 - ?= 26,而减若干个的1的操作肯定是在乘2之前完成的,所以我们将X与Y的差值分配到2的幂次方(幂次方就是之前使得X大于Y乘以2的次数),就可以知道当前减去多少1了,我们的目的是尽可能以最少的步数使得X逼近Y(相减的时候先减去2的最多的幂次方)

3. 代码如下:

迭代(逆向思维):

class Solution:
    def brokenCalc(self, X: int, Y: int) -> int:
        if X >= Y: return X - Y
        count = 0
        while X < Y:
            if Y % 2 == 0: Y //= 2
            else:
                Y += 1
            count += 1
        return X - Y + count

递归(逆向思维):

class Solution:
    def brokenCalc(self, X: int, Y: int) -> int:
        res = 0
        if X >= Y: return X - Y
        if Y % 2 == 0: return 1 + self.brokenCalc(X, Y // 2)
        else: return 1 + self.brokenCalc(X, Y + 1)

正向思维:

class Solution:
    def brokenCalc(self, X: int, Y: int) -> int:
        count1 = 0
        while X < Y:
            X *= 2
            count1 += 1
        # 计算出最终使得X>Y乘以2的最多的次数, 如果Y是X乘以2的幂次方那么最终返回count1即可
        if X == Y: return count1
        # 逆序遍历: 依次减去对应的倍数, 2 ** count1表示2的count1次方
        X -= Y
        base, count2 = 2 ** count1, 0
        # 下面尝试在乘以2之前减去对应的2的倍数
        while base >= 0:
            count2 += X // base
            X %= base
            base //= 2
            if X == 0: break
        return count1 + count2

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值