题目
小U有一个整数数组 nums 和一个整数 k。他可以执行以下操作恰好 k 次:选择某个下标 i 并将 nums[i] 替换为 -nums[i]。同一个下标 i 可以被多次选择进行操作。
小U想知道,通过这种方式修改数组后,能够得到的数组可能的最大和是多少。
解题思路
-
操作的效果:
- 每次选择一个下标
i,将nums[i]替换为-nums[i]。这样可以将负数变为正数,或者将正数变为负数。 - 目标是最大化数组的和,因此显然我们应尽量将负数变为正数。
- 每次选择一个下标
-
最优操作顺序:
- 首先应对数组中的 负数 进行操作,将它们变为正数,以增加数组的和。
- 如果
k次操作后还有剩余的操作次数(即k没有完全消耗),可以考虑将某些已经是正数的元素再次取反。对于已经是正数的元素,再取反会减少和,因此尽量减少这种操作。
-
操作步骤:
- 第一步:先对所有负数进行操作,尽可能将它们变为正数。
- 第二步:如果还有剩余的操作次数,且剩余的操作次数为奇数,则选择最小的绝对值进行再次取反,因为最小的绝对值带来的损失最小。
-
排序的使用:
- 可以将数组中的元素按绝对值大小排序,这样我们可以优先选择绝对值较大的元素进行操作,以便最大化总和。
豆包MarsCode AI针对我们的思路补充
1. 操作的效果:
每次操作的核心是将数组中的某个元素 nums[i] 取负,即变成 -nums[i]。这会导致:
- 如果该元素是负数,取负后变为正数,数组的总和增加。
- 如果该元素是正数,取负后变为负数,数组的总和减少。
因此,我们希望尽量选择将负数变为正数,因为这会增加数组的总和。
2. 操作的优先级:
我们需要根据每个操作对数组总和的影响来决定操作顺序。最优策略是:
- 优先处理负数: 负数取负后会变为正数,从而增加数组的总和。所以,我们优先将负数变为正数。
- 然后处理正数: 如果操作次数
k还剩余,且所有负数都已经处理完,剩下的操作可能会对正数产生影响。我们应该选择绝对值最小的元素进行反转,因为反转绝对值最小的元素对总和的影响最小。
3. 如何决定操作顺序:
我们可以通过以下步骤来决定最优的操作顺序:
- 第一步:排序:将数组按照元素的 绝对值 排序,优先处理绝对值大的元素。这样可以确保我们对影响最大的元素进行反转,从而尽可能最大化数组的和。
- 第二步:执行反转操作:从排序后的数组中选择元素进行反转。首先反转负数,将它们变为正数。每次反转一个负数时,
k减少1。 - 第三步:剩余操作处理:如果
k还有剩余,且k为奇数(即剩余的操作次数为奇数),我们就要对剩下的最小绝对值的元素进行反转,以最小化对数组总和的负面影响。
4. 边界情况:
- 如果数组中没有负数,所有操作只能对正数进行反转,那么我们应该确保尽量少反转大数,因为这会导致和下降。
- 如果
k已经用完,那么我们无需再做任何操作。
代码展示
def solution(nums: list, k: int) -> int:
# 先将数组按绝对值大小排序,绝对值大的元素优先被操作
nums.sort(key=abs, reverse=True)
# 对数组进行操作
for i in range(len(nums)):
if k == 0:
break
# 如果当前元素是负数,且我们还有操作次数,进行反转
if nums[i] < 0:
nums[i] = -nums[i]
k -= 1
# 如果操作次数 k 剩余为奇数,选择最小的元素反转一次
if k % 2 == 1:
nums.sort() # 重新排序,这时最小的元素在前
nums[0] = -nums[0] # 将最小元素反转一次
# 返回数组的总和
return sum(nums)
# 测试用例
if __name__ == '__main__':
print(solution(nums=[-4, 2, 3], k=1) == 9) # 预期输出:9
print(solution(nums=[-3, -1, 0, 3], k=3) == 7) # 预期输出:7
print(solution(nums=[-2, -3, -1, 5, -4], k=2) == 9) # 预期输出:9
时间复杂度分析
- 排序:
O(n log n),其中n是数组的大小。 - 遍历数组:
O(n),在最多遍历一遍数组时进行操作。 - 总的时间复杂度为
O(n log n),其中n是数组的长度。