修改数组求最大和问题 | 豆包MarsCode AI刷题

123 阅读3分钟

修改数组求最大和问题

在处理数组相关问题时,我们经常面临如何通过操作优化结果的挑战。在这一问题中,小U需要在给定的整数数组中,通过恰好 k 次翻转操作(负数变正,正数变负)来寻找可能获得的最大和。这不仅要求我们有效计算初始和,还需灵活使用排序和贪心策略来优先处理影响总和最大的元素。最终的目标是通过合理选择翻转策略,确保实现最佳的结果。

一、问题描述

小U有一个整数数组 nums 和一个整数 k。他可以执行以下操作恰好 k 次:选择某个下标 i 并将 nums[i] 替换为 -nums[i]。同一个下标 i 可以被多次选择进行操作。 小U想知道,通过这种方式修改数组后,能够得到的数组可能的最大和是多少。

二、解题思路

  1. 计算原始数组的总和: 找到翻转前的总和。
  2. 选择翻转的顺序:通过绝对值排序优先翻转影响较大的负数。
  3. 更新和:逐步更新总和并跟踪剩余的翻转次数。
  4. 处理剩余的翻转次数:如果还有剩余的翻转,我们需要考虑如何使用这些翻转来最小化损失。

核心思想

使用贪心算法,我们可以优先翻转绝对值较大的负数,以此来最大化总和。同时,如果有剩余的翻转机会,我们需要谨慎处理,因为再翻转一次可能会导致整体和的下降。

三、解题步骤

步骤 1: 计算原始数组的总和

首先计算数组中所有元素的和,以便在后续操作中作为基准。

步骤 2: 将数组按绝对值排序

为了找到最优的翻转选择,需要将数组中的元素按绝对值排序,这样可以确保在开始时优先翻转绝对值最大的元素。

步骤 3: 执行翻转操作

遍历排好序的数组,在每次选择时,尽量将绝对值最大的元素替换为其相反数:

  • 如果当前元素为负数,翻转它并增加总和。
  • 如果当前元素为正数,也可选择翻转它。

对于每个翻转,减少一次可用的 k

步骤 4: 处理剩余的 k 次翻转操作

当完成所有需要翻转的负数后,如果仍然有剩余的 k 次翻转:

  • 如果 k 为奇数,则需要选择绝对值最小的元素进行一次翻转(因为偶数次翻转不会影响和)。

四、代码实现

以下是用Go语言实现的完整函数:

func maxSumAfterKNegations(nums []int, k int) int {
    // 计算原始总和
    originalSum := 0
    for _, num := range nums {
        originalSum += num
    }

    // 按绝对值大小排序
    sort.Slice(nums, func(i, j int) bool {
        return math.Abs(float64(nums[i])) > math.Abs(float64(nums[j]))
    })

    // 尝试翻转负数以最大化总和
    for i := 0; i < len(nums); i++ {
        if k > 0 && nums[i] < 0 {
            originalSum -= 2 * nums[i] // 更新总和
            k--
        }
    }

    // 如果仍有剩余的 k 次翻转操作
    if k > 0 && k % 2 == 1 {
        // 找到绝对值最小的元素
        minAbsNum := math.MaxInt32
        for _, num := range nums {
            if absNum := int(math.Abs(float64(num))); absNum < minAbsNum {
                minAbsNum = absNum
            }
        }
        originalSum -= 2 * minAbsNum // 翻转绝对值最小的元素
    }

    return originalSum
}

五、复杂度分析

  • 时间复杂度:

    • 外层循环遍历数组长度 n,内层最多遍历 n(由于排序),因此整体时间复杂度为 O(nlogn)。
  • 空间复杂度:

    • 由于使用了一个额外的数组来存储排序后的结果,空间复杂度为 O(n)。

六、总结

本问题结合了贪心算法与简单的数组操作,通过合理选择翻转元素以最大化数组的总和。理解这一过程不仅能帮助我们解决本题,还能为相关的数组和动态规划问题打下基础。