翻转子数组求最大子数组和| 豆包MarsCode AI 刷题

86 阅读3分钟

问题描述

给定一个数组 arr,可以选择一个区间 [i, j],将该区间内的元素翻转(即变成相反数),然后重新计算最大子数组和。目标是找到一个最优的翻转操作,使得数组的最大子数组和达到最大值。


解题思路

  1. 最大子数组和的计算

    • 使用 Kadane 算法,线性时间内计算数组的最大子数组和。Kadane 算法通过维护两个变量 maxEndingHeremaxSoFar,动态更新当前的子数组和与全局最大值。
  2. 枚举所有翻转区间

    • 使用双层循环枚举所有可能的区间 [i, j],每次翻转区间后,重新计算翻转后的最大子数组和,保留全局最大值。
  3. 翻转区间的实现

    • 为了避免直接修改原数组,使用一个辅助数组来存储翻转后的结果。
  4. 前缀和的优化

    • 构造前缀和数组,快速求得任意区间的和,便于验证翻转的效果。

Java实现

以下是完整代码实现:

public class Main {
​
    // 使用 Kadane 算法计算最大子数组和
    private static int maxSubArraySum(int[] arr) {
        int maxSoFar = Integer.MIN_VALUE, maxEndingHere = 0;
​
        for (int num : arr) {
            maxEndingHere += num;
            if (maxSoFar < maxEndingHere) {
                maxSoFar = maxEndingHere;
            }
            if (maxEndingHere < 0) {
                maxEndingHere = 0;
            }
        }
        return maxSoFar;
    }
​
    public static int solution(int[] arr) {
        int n = arr.length;
        int originalMaxSum = maxSubArraySum(arr);
​
        // 计算前缀和
        int[] prefixSum = new int[n + 1];
        for (int i = 0; i < n; i++) {
            prefixSum[i + 1] = prefixSum[i] + arr[i];
        }
​
        int maxSum = originalMaxSum;
​
        // 枚举所有可能的翻转区间 [i, j]
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                // 翻转 [i, j] 区间
                int[] temp = arr.clone();
                reverse(temp, i, j);
                maxSum = Math.max(maxSum, maxSubArraySum(temp));
            }
        }
​
        return maxSum;
    }
​
    // 翻转数组区间 [start, end]
    private static void reverse(int[] arr, int start, int end) {
        while (start < end) {
            int temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
            start++;
            end--;
        }
    }
​
    public static void main(String[] args) {
        System.out.println(solution(new int[] { 1, 2, 3, -1, 4 }) == 10);
        System.out.println(solution(new int[] { -1, -2, -3, 4, 5 }) == 9);
    }
}

关键逻辑解析

  1. Kadane算法

    • 如果没有翻转操作,直接调用 maxSubArraySum 就可以得出最大子数组和。
    • 时间复杂度为 (O(N))。
  2. 翻转子数组

    • 双层循环枚举区间 [i, j],在每个区间上执行翻转操作。
    • 翻转操作的时间复杂度为 (O(N)),双层循环使得枚举的复杂度为 (O(N^2))。
  3. 结合前缀和

    • 在翻转区间时,直接利用前缀和计算 [i, j] 的和,并快速更新翻转后的数组。
    • 但此实现中直接克隆了原数组,因此未完全优化。

性能与优化

当前实现的时间复杂度为 (O(N^3)),因为:

  1. 双层循环的复杂度为 (O(N^2))。
  2. 每次翻转后需重新计算最大子数组和,复杂度为 (O(N))。

可以通过以下方式优化:

  1. 动态维护翻转增益

    • 利用 Kadane 算法的变种,动态记录翻转区间带来的增益,避免每次重新计算整个数组。
  2. 一次遍历更新翻转后的数组

    • 在翻转区间时,直接更新翻转后的最大子数组和,而不是完全重新计算。

测试用例与结果验证

对于数组 {1, 2, 3, -1, 4}

  • 初始最大子数组和为 9
  • 翻转区间 [3, 3] 后,最大子数组和变为 10

对于数组 {-1, -2, -3, 4, 5}

  • 初始最大子数组和为 9
  • 不需要任何翻转,结果仍为 9