LeetCode 2749.得到整数零需要执行的最少操作数:很独特的一道数学题(多公式硬讲——一步步还真能看懂)
【LetMeFly】2749.得到整数零需要执行的最少操作数:很独特的一道数学题(多公式硬讲——一步步还真能看懂)
力扣题目链接:https://leetcode.cn/problems/minimum-operations-to-make-the-integer-zero/
给你两个整数:num1
和 num2
。
在一步操作中,你需要从范围 [0, 60]
中选出一个整数 i
,并从 num1
减去 2i + 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 <= 109
-109 <= num2 <= 109
解题方法:数学
这个题解我自己也看了很多遍,如果你想搞懂这道题,请静下心来仔细读读,相信你一定可以搞懂这道题的!哪里不懂欢迎留言。
k范围浅分析
假设num1num1num1减去kkk次2i+num22^i+num22i+num2后变成了000,那么有:
num1−k∗num2=2i1+2i2+...+2iknum_1 - k * num_2 = 2^{i_1} + 2^{i_2} + ... + 2^{i_k}num1−k∗num2=2i1+2i2+...+2ik
见到2i1+2i2+...+2ik2^{i_1} + 2^{i_2} + ... + 2^{i_k}2i1+2i2+...+2ik应该很敏感才对啊,这不是222的幂之和么,只不过加数可以重复。
令等式左边的num1−k∗num2=xnum_1 - k * num_2 = xnum1−k∗num2=x,那么想让右边的2i1+2i2+...+2ik=x2^{i_1} + 2^{i_2} + ... + 2^{i_k}=x2i1+2i2+...+2ik=x,kkk的合法范围是多少?
已知2i+1=2i+2i2^{i+1}=2^i+2^i2i+1=2i+2i,所以想让可以使等式成立的kkk比较大的话,可以把一个2i+12^{i+1}2i+1可以拆成两个2i2^i2i,最小拆成2=1+12=1+12=1+1为止。也就是说,xxx最多可以由1+1+1+⋯+11+1+1+\dots+11+1+1+⋯+1(共xxx个)组成,也就是说kkk的最大值是xxx。
那么最小值呢?例如5=101(2)=22+205=101_{(2)}=2^2+2^05=101(2)=22+20,kkk最小值为222。也就是说对于xxx,kkk的最小值为xxx二进制下111的个数。
综上,只要popcount(x)≤k≤xpopcount(x)\leq k\leq xpopcount(x)≤k≤x,就能找到一种方法使得2i1+2i2+...+2ik=x2^{i_1} + 2^{i_2} + ... + 2^{i_k}=x2i1+2i2+...+2ik=x。(其中popcount(x)popcount(x)popcount(x)是xxx二进制下111的个数,x>0x\gt 0x>0。
请记住这两个条件:
- k≤xk\leq xk≤x
- k≥popcount(x)k\geq popcount(x)k≥popcount(x)
枚举k
但是别忘了,xxx中还含有变量kkk呢!其中x=num1−k∗num2x=num_1 - k * num_2x=num1−k∗num2。想要判断是返回kkk还是返回−1-1−1,还得看能不能找到一个kkk使得popcount(x)≤k≤xpopcount(x)\leq k\leq xpopcount(x)≤k≤x,其中x=num1−k∗num2x=num_1 - k * num_2x=num1−k∗num2。
最简单的办法就是暴力枚举。令kkk从111开始枚举,1,2,⋯1, 2, \cdots1,2,⋯,直到kkk超出“上界”,即k>xk\gt xk>x,也就是说k>num1−k∗num2k\gt num_1 - k * num_2k>num1−k∗num2停止。
这里你一定会思考,现在kkk超出上界了,那么我继续增大kkk的话,待会儿kkk会不会就不超出上界了呢?
为了探究这个问题,我们把kkk的合法范围处理一下:k≤num1−k∗num2⇔k∗(1+num2)≤num1k\leq num_1 - k * num_2\Leftrightarrow k*(1+num_2)\leq num_1k≤num1−k∗num2⇔k∗(1+num2)≤num1。
- 当1+num2≤01+num_2\leq 01+num2≤0时,由于数据范围限制num1≥1num_1\geq 1num1≥1,所以k∗(1+num2)≤num1k*(1+num_2)\leq num_1k∗(1+num2)≤num1恒成立;
- 当1+num2>01+num_2\gt 01+num2>0时,可得k≤num11+num2k\leq \frac{num_1}{1+num_2}k≤1+num2num1时k∗(1+num2)≤num1k*(1+num_2)\leq num_1k∗(1+num2)≤num1成立,一旦kkk大于num11+num2\frac{num_1}{1+num_2}1+num2num1该等式就再也不会成立;
综上,符合假设,不考虑实际算力限制下的超时问题的话,kkk从1,2,⋯1,2,\cdots1,2,⋯枚举到k>xk\gt xk>x为止,如果存在kkk满足第二个条件即k≥popcount(x)k\geq popcount(x)k≥popcount(x),我们就能找到了符合条件的kkk;否则,这个枚举范围内没有满足第二个条件的kkk就返回−1-1−1。
那么kkk的范围究竟是多少呢?暴力枚举会不会超时呢?你别说,还真不会。
因为要想找到一个满足两个条件的kkk,除了一个限制枚举范围的k≤xk\leq xk≤x外,还有一个条件,就是k≥popcount(x)k\geq popcount(x)k≥popcount(x)。
我们一直担心的就是会不会k≤xk\leq xk≤x这个条件范围太大,导致kkk一直从111枚举到一个非常大的数字(甚至是无穷?),从而超时。
但是别忘了第二个条件k≥popcount(x)k\geq popcount(x)k≥popcount(x)是非常容易满足的,要知道popcount(x)popcount(x)popcount(x)可是xxx在二进制下111的个数,所以先告诉你结论再去证明:kkk最大枚举到几十就会满足第二个条件了。
kkk的量级是几十(按10310^3103),numnumnum的量级是10910^9109,所以x=num1−k∗num2x=num_1 - k * num_2x=num1−k∗num2的量级最多为101210^{12}1012,2402^40240已经大于101210^{12}1012了(不信可以执行下
python -c "print(len(str(2**40)))"
试试),xxx二进制下位数不超过404040位,就找到满足第二个条件的kkk了。
也就是说实际kkk从111开始枚举,最多枚举到404040,就知道答案了,时间复杂度甚至可以认为是O(1)O(1)O(1)。
- 时间复杂度O(1)O(1)O(1)
- 空间复杂度O(1)O(1)O(1)
然后结合实现代码说一下:
int makeTheIntegerZero(int num1, int num2) {for (int k = 1; k <= num1 - (long long)num2 * k; k++) {if (k >= __builtin_popcountll(num1 - (long long)num2 * k)) {return k;}}return -1;
}
关于这个循环的范围:
- 如果1+num2>11+num_2\gt 11+num2>1,那么要么
for
循环先终止返回-1
(如num1=16, num2=10
会在k=2
时结束for循环),要么if
条件先命中return k
。 - 如果1+num2≤01+num_2\leq 01+num2≤0,
for
循环相当于while true
,但是里面的if
条件一定会很快满足,并return k
。
总之kkk枚举不会超过404040次。解喽。
AC代码
C++
/** @Author: LetMeFly* @Date: 2025-09-05 18:29:32* @LastEditors: LetMeFly.xyz* @LastEditTime: 2025-09-05 21:33:38*/
#if defined(_WIN32) || defined(__APPLE__)
#include "_[1,2]toVector.h"
#endif/*
nums1 - k * nums2 = 2^{i_1} + 2^{i_2} + ... + 2^{i_k}
*/
class Solution {
public:int makeTheIntegerZero(int num1, int num2) {for (int k = 1; k <= num1 - (long long)num2 * k; k++) {if (k >= __builtin_popcountll(num1 - (long long)num2 * k)) {return k;}}return -1;}
};
同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~
千篇源码题解已开源