贪心算法 坏了的计算器
题目
难度:简单
在显示着数字的坏计算器上,我们可以执行以下两种操作:
双倍(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
题解一
为什么这道题采用逆向思维更优?
1.刚开始的时候觉得很简单,用了两个while,结果报错了。因为可以先减去再去乘以就会节省次数
正向思维:在X/Y时要实现操作数最小,要将X逼近Y的1/2值或1/4值或1/8值或...再进行*2操作,难点在于要判断要逼近的是1/2值还是1/4值还是其他值,逻辑复杂 逆向思维:在Y>X时Y只管/2,到了Y\<X时在+1逼近 说白了就是,正向思维采用的是先小跨度的-1操作,再大跨度的*2操作;逆向思维采用的是先大跨度的/2操作,再小跨度的-1操作 然而事实上往往是先大后小的解决问题思维在实现起来会比较简单 cur=y;存储yn=0;次数
2.什么时候可以先减去再去乘以?Y是一个偶数
3.所以先把cur+1成为偶数
4.再/2处理,小于X之后跳出循环
5.这时候n+x-cur即为结果x-cur
为x与cur之间还需要进行的几次操作
class Solution {
public int brokenCalc(int X, int Y) {
int cur=Y,n=0;
while(X<cur){
n++;
if(cur%2==1){
cur++;
}
else{
cur/=2;
}
}
return n+X-cur;
}
}
解法二:递归
class Solution {
public int brokenCalc(int X, int Y) {
if (X >= Y) {
return X - Y;
}
if (Y % 2 == 1) {
return 2 + brokenCalc(X, (Y + 1) / 2);
} else {
return 1 + brokenCalc(X, Y / 2);
}
}
}
解法三:分步
只需要讨论 Y > X时的情况。分为两步统计, cnt1为多少个乘法,cnt2为多少个减法。 显然我们必须把 X 乘到恰好比 Y 大的数,否则再怎么减也达不到要求……因此先求 cnt1. 那么,关键是 cnt2 怎么求呢?我们假设减法穿插在各个乘法之间,如果在第一次乘法前减,那么最终等价于减去 2cnt12^{cnt1}2cnt1, 如果在第二次乘法前减,最终等价于减去 2cnt1−12^{cnt1 - 1}2cnt1−1,以此类推。由于每次可以减多个1,因此最终要乘个系数,减了 a 2cnt12^{cnt1}2cnt1 + b 2cnt1−12^{cnt1 - 1}2cnt1−1 + .... 那么这个系数 a,b,c等等是多少呢,贪心即可,a越大越好,其次到b, c...
class Solution {
public int brokenCalc(int X, int Y) {
if (Y <= X) return X - Y;
int cnt1 = 0;
while (X < Y) {
X *= 2;
cnt1 ++;
}
if (X == Y) return cnt1;
int r = X - Y;
int cnt2 = 0;
for (int i = cnt1; i >= 0; i --) {
int t = (int)Math.pow(2, i);
int coeff = r / t;
r = r % t;
cnt2 += coeff;
if (r == 0) break;
}
return cnt1 + cnt2;
}
}