青训营X豆包MarsCode 技术训练营第十课 | 豆包MarsCode AI 刷题

62 阅读4分钟

第499题 非重叠子数组的最大和

1.题目分析

题目要求在一个整数数组 nums 中,找到两个长度分别为 firstLensecondLen 的非重叠子数组,使得这两个子数组的元素和最大。
需要注意的是:

  • 这两个子数组不能有任何重叠部分。
  • 子数组的顺序没有限制,即长度为 firstLen 的子数组可以在 secondLen 的子数组前,也可以在其后。

因此,我们需要找到一个算法,可以分别考虑两种情况,并得到最大和。

2.解题思路

解题思路可以分为以下几个步骤:

  • 先分别计算 firstLensecondLen 长度的子数组在不同位置的最大和。具体来说:

    • 计算数组中从头到尾每个位置的前 firstLen 长度的子数组的最大和。
    • 计算数组中从尾到头每个位置的前 secondLen 长度的子数组的最大和。
    • 将上述步骤反过来再做一次,即计算 secondLen 在前、firstLen 在后的情况。
  • 最后,遍历数组,分别尝试两种组合:

    • firstLen 子数组在前,secondLen 子数组在后。
    • secondLen 子数组在前,firstLen 子数组在后。
  • 取两种组合中的最大值,得到结果。

3.解题代码

def solution(nums: list, firstLen: int, secondLen: int) -> int:
    def max_sum_subarray(arr, k):
        # 初始化第一个窗口的和
        max_sum = sum(arr[:k])
        current_sum = max_sum
        max_sums = [0] * len(arr)
        max_sums[k - 1] = max_sum

        # 滑动窗口遍历数组,更新每个位置的最大和
        for i in range(k, len(arr)):
            current_sum += arr[i] - arr[i - k]
            max_sum = max(max_sum, current_sum)
            max_sums[i] = max_sum
        
        return max_sums

    # 计算 leftMax 和 rightMax 数组,用于组合计算
    left_first = max_sum_subarray(nums, firstLen)
    right_second = max_sum_subarray(nums[::-1], secondLen)[::-1]

    left_second = max_sum_subarray(nums, secondLen)
    right_first = max_sum_subarray(nums[::-1], firstLen)[::-1]

    max_sum = 0
    # firstLen 在前,secondLen 在后
    for i in range(firstLen - 1, len(nums) - secondLen):
        max_sum = max(max_sum, left_first[i] + right_second[i + 1])

    # secondLen 在前,firstLen 在后
    for i in range(secondLen - 1, len(nums) - firstLen):
        max_sum = max(max_sum, left_second[i] + right_first[i + 1])

    return max_sum

if __name__ == '__main__':
    print(solution(nums=[0, 6, 5, 2, 2, 5, 1, 9, 4], firstLen=1, secondLen=2) == 20)
    print(solution(nums=[3, 8, 1, 3, 5, 2, 1, 0], firstLen=3, secondLen=2) == 21)
    print(solution(nums=[2, 1, 4, 3, 5, 9, 5, 0, 3, 8], firstLen=4, secondLen=3) == 33)

4.模块解释

4.1 max_sum_subarray 函数

  • 功能:计算每个位置的前 k 个元素的最大和。

  • 实现细节

    • 使用滑动窗口方法,先计算第一个窗口的和。
    • 然后滑动窗口,从第 k 个元素开始计算窗口和,并记录每个位置的最大和。
    • 最后返回一个数组 max_sums,其中 max_sums[i] 表示从数组开始到位置 i 的最大子数组和。

4.2 left_firstright_second

  • 功能:分别计算 firstLensecondLen 长度的子数组的最大和,并考虑顺序。

  • 实现细节

    • left_first:使用 max_sum_subarray 计算 firstLen 长度的子数组在从头开始到当前位置的最大和。
    • right_second:使用 max_sum_subarray 计算 secondLen 长度的子数组从尾部到当前位置的最大和(通过逆序数组再逆序回来实现)。
    • 这样可以将 firstLen 在前,secondLen 在后的情况组合起来。

4.3 left_secondright_first

  • 功能:类似于 left_firstright_second,但考虑 secondLen 在前,firstLen 在后的情况。

  • 实现细节

    • left_second:计算 secondLen 长度的子数组的最大和在从头开始到当前位置的最大和。
    • right_first:计算 firstLen 长度的子数组从尾部到当前位置的最大和。
    • 这样可以将 secondLen 在前,firstLen 在后的情况组合起来。

4.4 最大和计算

  • 功能:遍历数组,分别考虑两种组合方式,得到最大和。

  • 实现细节

    • 第一种情况是 firstLen 子数组在前,secondLen 子数组在后。我们使用 left_first[i] + right_second[i + 1] 表示。
    • 第二种情况是 secondLen 子数组在前,firstLen 子数组在后。我们使用 left_second[i] + right_first[i + 1] 表示。
    • 取这两种情况中的最大值作为结果返回。

5.结论

该算法使用滑动窗口和前缀和的思想,分别计算了两种顺序的非重叠子数组最大和。通过对 firstLensecondLen 的顺序进行分情况处理,确保找到了最大和的组合。最终的时间复杂度为 O(n)O(n)O(n),因为我们只需要遍历数组两次。算法效率较高,能够满足大多数题目的规模要求。