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 <= X <= 10^9
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