Problem: 2749. 得到整数零需要执行的最少操作数
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(1)
- 空间复杂度:O(1)
整体思路
这段代码旨在解决一个具有数学和位运算性质的问题:给定两个整数 num1
和 num2
,找到最小的正整数 k
,使得 num1
可以表示为 k
个整数的和,其中每个整数的形式都是 num2 + 2^i
(i >= 0
)。如果找不到这样的 k
,则返回 -1。
该算法将问题进行了巧妙的数学转换,然后通过一个循环来枚举并检验所有可能的 k
值。
-
问题的数学转换:
- 题目要求
num1 = (num2 + 2^{i_1}) + (num2 + 2^{i_2}) + ... + (num2 + 2^{i_k})
。 - 将上式重新整理,可以得到
num1 = k * num2 + (2^{i_1} + 2^{i_2} + ... + 2^{i_k})
。 - 进一步变形,我们得到
num1 - k * num2 = 2^{i_1} + 2^{i_2} + ... + 2^{i_k}
。 - 令
x = num1 - k * num2
。问题就转化为了:找到最小的正整数k
,使得x
可以表示为k
个 2的幂的和。
- 题目要求
-
x
的性质与约束:- 一个正整数
x
可以表示为m
个 2的幂的和,当且仅当x
的二进制表示中 1 的个数(即Long.bitCount(x)
)小于或等于m
,并且x
本身 大于或等于m
。Long.bitCount(x) <= m
:Long.bitCount(x)
是将x
表示为 2的幂的和所需的最少项数。例如,7 (111_2) = 4+2+1
,最少需要3个2的幂。我们可以通过将一个大的2的幂拆分成两个小的(如2^i = 2^{i-1} + 2^{i-1}
)来增加项数。所以,只要m
不小于最少项数,就总能凑出m
项。x >= m
:因为每个 2的幂2^i
至少是1
(2^0
),所以m
个 2的幂的和至少是m
。因此x
必须大于或等于m
。
- 一个正整数
-
算法的枚举与检验逻辑:
- 基于以上的转换和性质,算法现在只需要找到最小的正整数
k
,满足以下两个条件:
a.x = num1 - k * num2 >= k
b.Long.bitCount(x) <= k
- 枚举
k
:代码通过一个for
循环,从k=1
开始枚举。 - 循环上限:循环的上限设置为 60。这是一个基于
num1
和num2
的数据范围(通常是10^9
级别)得出的一个足够大的、安全的上界。2^60
已经远超10^18
,通常可以覆盖long
类型的计算范围。 - 检验:在每次循环中:
- 计算
x = num1 - 1L * num2 * k
。使用long
类型是为了防止计算过程中发生整数溢出。 - 检查第一个条件
x < k
。如果不满足(注意代码中是x<k
就continue
,这等价于要求x>=k
),则这个k
无效,继续尝试下一个。 - 计算
x
的二进制表示中 1 的个数min = Long.bitCount(x)
。 - 检查第二个条件
k >= min
。如果满足,说明当前的k
是一个有效的解。因为我们是从小到大枚举k
,所以第一个找到的有效解就是最小的,直接return k
。
- 计算
- 基于以上的转换和性质,算法现在只需要找到最小的正整数
-
无解情况:
- 如果循环结束(
k
从 1 到 60 都试过了)还没有找到一个有效的k
,那么就认为问题无解,返回-1
。
- 如果循环结束(
完整代码
class Solution {/*** 找到最小的正整数 k,使得 num1 可以表示为 k 个 (num2 + 2^i) 形式的整数之和。* @param num1 目标整数* @param num2 基础整数* @return 最小的正整数 k,如果不存在则返回 -1*/public int makeTheIntegerZero(int num1, int num2) {// 循环枚举所有可能的 k 值。// 上限 60 是一个基于数据范围的经验值,对于 int 范围的 num1, num2 足够。for (int k = 1; k <= 60; k++) {// 步骤 1: 根据数学转换计算 x = num1 - k * num2// 使用 long 类型防止计算过程中溢出long x = 1L * num1 - 1L * num2 * k;// 步骤 2: 检验 k 是否满足两个核心条件// 条件 1: x 必须能被表示成 k 个 2的幂的和。// 一个必要条件是 x >= k (因为每个 2^i >= 1)。// 如果 x < k,则当前 k 无效,继续下一个。if (x < k) {// 原代码的 continue 逻辑可以合并到下面的 if 判断中,// 即 if (x >= k && k >= min) { return k; }// 这里为了忠于原代码结构,保持原样。continue;}// 计算将 x 表示为 2的幂的和所需的最少项数int min = Long.bitCount(x);// 条件 2: 表示 x 所需的项数必须能够凑成 k。// 这要求 k 必须大于或等于所需的最少项数 (min)。if (k >= min) {// 因为 k 是从小到大枚举的,所以第一个满足条件的 k 就是最小的。return k;}}// 如果循环结束仍未找到解,则返回 -1return -1;}
}
时空复杂度
时间复杂度:O(1)
- 循环:算法的核心是一个
for
循环,该循环的执行次数是一个固定的常数(从 1 到 60)。 - 循环内部操作:
- 在每次迭代中,执行的操作包括:长整型乘法、减法、
Long.bitCount()
、比较。 Long.bitCount()
方法在大多数现代CPU上都有对应的硬件指令(如POPCNT
),其执行时间可以认为是 O(1)。即使是软件实现,其复杂度也与long
的位数(64位)相关,是一个常数。- 因此,循环内部的所有操作都是常数时间的。
- 在每次迭代中,执行的操作包括:长整型乘法、减法、
综合分析:
算法的执行时间是 (一个固定的常数次数) * (常数时间操作)。因此,其时间复杂度为 O(1)。
空间复杂度:O(1)
- 存储分析:
- 算法在执行过程中只使用了几个基本类型的变量(
k
,x
,min
)。 - 这些变量的数量是固定的,不随输入
num1
和num2
的大小而改变。
- 算法在执行过程中只使用了几个基本类型的变量(
综合分析:
算法没有使用任何与输入规模成比例的额外数据结构。因此,其额外辅助空间复杂度为 O(1)。