Medium
题目描述
给你一个整数数组 nums。你有一个长度相同的整数数组 arr,其所有值最初都设为 0。你还有以下修改函数:
你可以调用函数使 arr 中任意一个元素加 1。 你可以调用函数使 arr 中所有元素同时乘 2。
你想要使用最少的函数调用次数将 arr 转换为 nums。
返回将数组 arr 转换为数组 nums 所需的最少函数调用次数。
答案保证在 32 位有符号整数范围内。
示例 1:
输入:nums = [1,5]
输出:5
解释:给第二个元素加 1:[0, 0] 变为 [0, 1](1 次操作)。
将所有元素乘 2:[0, 1] -> [0, 2] -> [0, 4](2 次操作)。
给两个元素都加 1:[0, 4] -> [1, 4] -> [1, 5](2 次操作)。
总操作次数:1 + 2 + 2 = 5。
示例 2:
输入:nums = [2,2]
输出:3
解释:给两个元素都加 1:[0, 0] -> [0, 1] -> [1, 1](2 次操作)。
将所有元素乘 2:[1, 1] -> [2, 2](1 次操作)。
总操作次数:2 + 1 = 3。
示例 3:
输入:nums = [4,2,5]
输出:6
解释:(初始)[0,0,0] -> [1,0,0] -> [1,0,1] -> [2,0,2] -> [2,1,2] -> [4,2,4] -> [4,2,5](目标数组)。
约束:
- 1 <= nums.length <= 10^5
- 0 <= nums[i] <= 10^9
提示:
- 反向思考:尝试从 nums 到 arr
- 尽可能多地除以 2,但只有当所有元素都是偶数时才能除以 2
解题思路
这题的关键是反向思考:从目标数组 nums 逆向推导到全 0 数组 arr。
核心思路是贪心算法:
- 除法操作:当数组中所有非零元素都是偶数时,我们可以将整个数组除以 2(相当于正向的乘 2 操作)
- 减法操作:对于奇数元素,我们必须先减 1 使其变为偶数(相当于正向的加 1 操作)
算法步骤:
- 统计所有元素的 1 的个数(即需要的减法操作次数)
- 找到数组中最大元素的二进制位数,这决定了最多需要多少次除法操作
- 不断检查数组是否全为 0:
- 如果有奇数,将其减 1
- 如果所有非零元素都是偶数,将所有元素除以 2
优化思路:我们可以直接通过位操作来计算:
- 加法操作次数 = 所有数字二进制表示中 1 的总个数
- 乘法操作次数 = 最大数字的二进制位数减 1(因为最后一步不需要乘 2)
这样可以避免模拟整个过程,直接得出答案。
代码实现
class Solution {
public:
int minOperations(vector<int>& nums) {
int addOps = 0; // 加法操作次数
int maxBits = 0; // 最大数字的位数
for (int num : nums) {
if (num == 0) continue;
// 统计1的个数(加法操作)
addOps += __builtin_popcount(num);
// 找最大数字的位数
int bits = 32 - __builtin_clz(num);
maxBits = max(maxBits, bits);
}
// 乘法操作次数 = 最大位数 - 1
int mulOps = maxBits > 0 ? maxBits - 1 : 0;
return addOps + mulOps;
}
};
class Solution:
def minOperations(self, nums: List[int]) -> int:
add_ops = 0 # 加法操作次数
max_bits = 0 # 最大数字的位数
for num in nums:
if num == 0:
continue
# 统计1的个数(加法操作)
add_ops += bin(num).count('1')
# 找最大数字的位数
bits = num.bit_length()
max_bits = max(max_bits, bits)
# 乘法操作次数 = 最大位数 - 1
mul_ops = max_bits - 1 if max_bits > 0 else 0
return add_ops + mul_ops
public class Solution {
public int MinOperations(int[] nums) {
int addOps = 0; // 加法操作次数
int maxBits = 0; // 最大数字的位数
foreach (int num in nums) {
if (num == 0) continue;
// 统计1的个数(加法操作)
addOps += CountBits(num);
// 找最大数字的位数
int bits = (int)Math.Floor(Math.Log2(num)) + 1;
maxBits = Math.Max(maxBits, bits);
}
// 乘法操作次数 = 最大位数 - 1
int mulOps = maxBits > 0 ? maxBits - 1 : 0;
return addOps + mulOps;
}
private int CountBits(int n) {
int count = 0;
while (n > 0) {
count += n & 1;
n >>= 1;
}
return count;
}
}
/**
* @param {number[]} nums
* @return {number}
*/
var minOperations = function(nums) {
let operations = 0;
let maxBits = 0;
for (let num of nums) {
let bits = 0;
let temp = num;
while (temp > 0) {
if (temp & 1) {
operations++;
}
temp >>= 1;
bits++;
}
maxBits = Math.max(maxBits, bits);
}
return operations + Math.max(0, maxBits - 1);
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n × log M) | n 为数组长度,M 为数组中最大值,需要统计每个数的二进制位 |
| 空间复杂度 | O(1) | 只使用常数额外空间 |