题目
给你两个整数:num1 和 num2 。
在一步操作中,你需要从范围 [0, 60] 中选出一个整数 i ,并从 num1 减去 2^i + num2 。
请你计算,要想使 num1 等于 0 需要执行的最少操作数,并以整数形式返回。
如果无法使 num1 等于 0 ,返回 -1 。
示例 1:
输入:num1 = 3, num2 = -2 输出:3 解释:可以执行下述步骤使 3 等于 0 :
- 选择 i = 2 ,并从 3 减去 22 + (-2) ,num1 = 3 - (4 + (-2)) = 1 。
- 选择 i = 2 ,并从 1 减去 22 + (-2) ,num1 = 1 - (4 + (-2)) = -1 。
- 选择 i = 0 ,并从 -1 减去 20 + (-2) ,num1 = (-1) - (1 + (-2)) = 0 。 可以证明 3 是需要执行的最少操作数。
示例 2:
输入:num1 = 5, num2 = 7 输出:-1 解释:可以证明,执行操作无法使 5 等于 0 。
提示:
1 <= num1 <= 10^9-10^9 <= num2 <= 10^9
解题思路
首先,我们对原等式进行变换: $$num1 - (2^{i_1} + num2) - (2^{i_2} + num2) - ... - (2^{i_k} + num2) = 0$$
将所有 num2 移项,得到: $$num1 - k * num2 = 2^{i_1} + 2^{i_2} + ... + 2^{i_k}$$
其中 k 是操作次数,也是我们需要求的最小值。
这个等式告诉我们,num1 - k * num2 必须可以表示为 k 个 2 的幂的和。
贪心算法
一个直观的想法是,每次都尽可能选择最大的 2^i 来减去,这样可以更快地接近 0。但这个方法在这里并不适用,因为它没有考虑到 k 个 num2 的和,k 是未知数。
正确的解题思路是,我们枚举操作次数 k,从 1 开始,直到找到第一个满足条件的 k。
具体
- 处理
num2的正负情况- 如果
num2是正数,那么每次操作num1都会减去2^i + num2,这个值总是大于 0。如果num1本身就是正数,那么num1只会越来越小。如果num1 - k * num2在某一步骤变为负数,那么num1将永远无法变成 0。我们需要确保num1 - k * num2始终是非负数,并且可以被表示为k个 2 的幂的和。 - 如果
num2是负数,那么每次操作num1都会减去2^i并加上一个正数|num2|。这意味着num1可能增加也可能减少。
- 如果
- 枚举操作次数
k- 我们从
k = 1开始,递增地尝试。对于每一个k,计算target = num1 - k * num2。 - 如果
target < k,则target无法表示为k个 2 的幂的和,因为每个2^i至少是 1 (2^0)。继续尝试下一个k。 - 如果
target是一个负数,并且num2是正数,那么num1永远无法变成 0。因为num1会一直减小,不可能再变回 0。 - 对于
target >= k的情况,我们需要判断target是否可以表示成k个 2 的幂的和。这可以通过检查target的二进制表示来完成。
- 我们从
- 检查
target的二进制表示target可以表示成k个 2 的幂的和,等价于target的二进制表示中,1 的个数(也叫 popcount 或 hamming weight)小于等于k。- 为什么?因为任何一个正整数都可以唯一地表示成若干个不重复的 2 的幂的和(即它的二进制表示)。例如,
13 = 8 + 4 + 1 = 2^3 + 2^2 + 2^0。13的二进制是1101,有 3 个 1。这表示13最少需要 3 个 2 的幂的和。 - 如果
target的 popcount 记为p,那么target可以表示为p个 2 的幂的和。 - 我们还可以通过拆分
2^i来增加项数。例如,2^3可以拆分成2^2 + 2^2,这样就增加了 1 项。所以,如果target的 popcount 是p,它就可以表示为p个、p+1个、p+2个... 的 2 的幂的和。 - 所以,我们只需要判断
p <= k且target >= k是否成立,公式如下:$$\mathrm{popcount}(t)\leq m\leq t$$
- 循环与终止条件
- 我们从
k = 1开始循环。 - 如果
k递增到某个值,使得num1 - k * num2成为一个非常大的负数(例如,num1 - k*num2 < -60),且num2是正数,那么我们就可以停止循环并返回 -1。因为num1 - k*num2变得越来越小,不满足target >= k。一个更严谨的判断是num1 - k*num2什么时候会小于k。 - 当
k满足(num1 - k*num2) >= k且popcount(num1 - k*num2) <= k时,我们找到了最小的操作次数,返回k。 - 如果
k循环到 60 仍然没有找到,num1 - k * num2可能会溢出long long的范围。但题目给出的i范围是[0, 60],这意味着我们最多可以使用 60 个 2 的幂,即2^60。一个稳健的办法是设定一个循环上限,例如k循环到60左右,如果还没有结果,可能就无法达成。更严谨的上限可以根据num1和num2的值来推导。当k足够大时,num1 - k*num2可能会远小于k,此时可以停止。
- 我们从
具体代码
class Solution {
public:
int makeTheIntegerZero(int num1, int num2) {
for(int k = 0; k <= 32; k++)
{
long long current = num1 - 1LL * k * num2;
if(current >= k)
{
int count = 0;
while(current > 0) // 这里的运算取巧
{
current &= current - 1;
count++;
}
if(count <= k)
return k;
}
}
return -1;
}
};