1558. 得到目标数组的最少函数调用次数

目录

【算法题解】LeetCode 1558变形:得到目标数组的最少函数调用次数(加法+乘法操作)

题目描述

输入输出示例

问题分析

这道题难点

解题思路

具体步骤

代码实现

复杂度分析

详细示例解析

示例 1

示例 2

解题方法对比

总结


【算法题解】LeetCode 1558变形:得到目标数组的最少函数调用次数(加法+乘法操作)


题目描述

给定一个长度为 n 的初始数组 arr,所有元素初始化为 0。目标是通过一系列操作将 arr 变成目标数组 nums

你可以对数组执行以下两种操作:

  1. 加法操作:选择一个连续子区间,对该区间所有元素加 1。
  2. 乘法操作:将整个数组乘以 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)。
  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)

正确高效,适应大规模

需对二进制理解较好


总结

这道题看似复杂,但利用二进制位拆解加法/乘法操作本质,巧妙地将复杂问题转换为对二进制的操作计数,既保证了算法效率,也保证了正确性。

如果想做这类“混合操作”的问题,一定要深入理解操作对应的数值和位运算关系,才能找到简洁而有效的解法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值