目录
【算法题解】LeetCode 1558变形:得到目标数组的最少函数调用次数(加法+乘法操作)
【算法题解】LeetCode 1558变形:得到目标数组的最少函数调用次数(加法+乘法操作)
题目描述
给定一个长度为 n 的初始数组 arr
,所有元素初始化为 0。目标是通过一系列操作将 arr
变成目标数组 nums
。
你可以对数组执行以下两种操作:
- 加法操作:选择一个连续子区间,对该区间所有元素加 1。
- 乘法操作:将整个数组乘以 2。
请你计算完成上述操作,使 arr
变成 nums
的最少操作次数。
输入输出示例
示例 1:
输入:nums = [1, 5]
输出:5
解释:
初始数组:[0,0]
1) 给第二个数加 1 :[0,0] -> [0,1](1次加法操作)
2) 将所有数字乘以 2 :[0,1] -> [0,2] -> [0,4](2次乘法操作)
3) 给两个数字加 1 :[0,4] -> [1,4] -> [1,5](2次加法操作)
总操作次数为:1 + 2 + 2 = 5
示例 2:
输入:nums = [2, 2]
输出:3
解释:
1) 给两个数字都加 1 :[0,0] -> [0,1] -> [1,1](2次加法操作)
2) 将所有数字乘以 2 :[1,1] -> [2,2](1次乘法操作)
总操作次数为 3
问题分析
这道题结合了两种操作,和经典的单纯“选择连续区间加 1”问题相比,多了对全数组乘以 2的操作。
- 加法操作可以用来增加数组中的单个位上的1。
- 乘法操作本质是对数组所有元素左移一位(相当于二进制中的乘 2)。
这道题难点
- 如何合理地将乘法操作和加法操作组合起来,减少操作次数?
- 乘法操作只能全体进行,而加法操作可以选择任意连续子区间。
- 数组元素可非常大(最高可达10^9),暴力模拟不可行。
解题思路
通过观察和题目示例,我们可以把每个元素拆成二进制来分析操作次数。
- 乘法操作的作用:相当于把所有元素的二进制整体向左移动一位,减少所有数的大小。
- 加法操作的作用:在二进制的某一位上对部分元素加 1。
换句话说:
- 乘法操作的次数 =
nums
中最大元素二进制长度 - 1(因为最低位不需要乘法) - 加法操作的次数 = 所有元素二进制中“1”的个数总和(对应需要在哪些位上做加法操作)
具体步骤
- 处理最低位的加法操作:
-
- 遍历数组,找到所有最低位为 1 的元素,执行加法操作(相当于将这些元素减去1)。
- 判断是否所有元素都是偶数:
-
- 如果是,可以执行一次乘法操作(对应把所有元素右移一位)。
- 重复步骤 1 和 2,直到所有元素归零。
代码实现
from typing import List
class Solution:
def minOperations(self, nums: List[int]) -> int:
res = 0
while any(nums): # 只要有非零元素
# 加法操作:对最低位为1的元素减1
for i in range(len(nums)):
if nums[i] & 1: # 判断最低位是否为1
res += 1
nums[i] -= 1
# 判断是否可以进行乘法操作(所有元素都为偶数或0)
if all(x == 0 or x % 2 == 0 for x in nums):
# 乘法操作:所有元素右移一位
nums = [x >> 1 for x in nums]
if any(nums): # 只有在数组未全部为0时计数乘法操作
res += 1
return res
复杂度分析
- 时间复杂度:O(n * log M),其中 n 是数组长度,M 是数组中最大元素的大小。每次循环中元素至少减半(右移),循环次数为二进制位数,乘以遍历所有元素。
- 空间复杂度:O(n),数组原地修改。
详细示例解析
示例 1
nums = [1,5]
二进制表示:
1 = 0001
5 = 0101
操作过程:
- 最低位为1,执行加法操作,对应元素减1:
[1,5] -> [0,4],加法次数 = 2(分别针对1和5)
- 现在检查是否都为偶数:
[0,4] 是偶数,执行乘法操作:
[0,4] -> [0,2],乘法次数 = 1
- 再次检查是否都为偶数:
[0,2] 是偶数,执行乘法操作:
[0,2] -> [0,1],乘法次数 = 2
- 最低位为1,执行加法操作:
[0,1] -> [0,0],加法次数 = 3
总操作次数 = 加法(2+1) + 乘法(2) = 5
示例 2
nums = [2,2]
二进制表示:
2 = 0010
操作过程:
- 最低位均为0,跳过加法操作
- 乘法操作:
[2,2] -> [1,1],乘法次数 = 1
- 最低位为1,执行加法操作:
[1,1] -> [0,0],加法次数 = 2
总操作次数 = 乘法(1) + 加法(2) = 3
解题方法对比
方法 | 说明 | 复杂度 | 优点 | 缺点 |
递归减最小值 + 分段递归 | 针对无乘法操作版本 | O(n²) | 逻辑直观 | 对带乘法操作无效,效率低 |
线性遍历加差值法(仅加法) | 适合无乘法操作版本 | O(n) | 实现简单 | 无法解决带乘法操作问题 |
位运算模拟法(当前解法) | 适合带加法+乘法操作 | O(n log M) | 正确高效,适应大规模 | 需对二进制理解较好 |
总结
这道题看似复杂,但利用二进制位拆解和加法/乘法操作本质,巧妙地将复杂问题转换为对二进制的操作计数,既保证了算法效率,也保证了正确性。
如果想做这类“混合操作”的问题,一定要深入理解操作对应的数值和位运算关系,才能找到简洁而有效的解法