翻转增益的最大子数组和 | 豆包MarsCode AI刷题

2 阅读6分钟

问题描述

小C面对一个由整数构成的数组,他考虑通过一次操作提升数组的潜力。这个操作允许他选择数组中的任一子数组并将其翻转,目的是在翻转后的数组中找到具有最大和的子数组。小C对这个可能性很感兴趣,并希望知道翻转后的数组中可能得到的最大子数组和是多少。

例如,数组是 1, 2, 3, -1, 4。小C可以选择翻转子数组 -1, 4 得到 1, 2, 3, 4, -1 或者翻转 1, 2, 3, -1 得到 -1, 3, 2, 1, 4,在这两种情况下,最大的子数组和都是 10。

备注:子数组 是数组中的一个连续部分。

输入

  • N: 数组的长度
  • data_array: 一个长度为 N 的整数数组

输出

请你返回执行翻转操作后(也可以选择不翻转),数组中可能获得的最大子数组和。


测试样例

样例1:

输入:N = 5,data_array = [1, 2, 3, -1, 4]
输出:10

样例2:

输入:N = 4,data_array = [-3, -1, -2, 3]
输出:3

样例3:

输入:N = 3,data_array = [-1, -2, -3]
输出:-1

样例4:

输入:N = 6,data_array = [-5, -9, 6, 7, -6, 2]
输出:15

样例5:

输入:N = 7,data_array = [-8, -1, -2, -3, 4, -5, 6]
输出:10

题目解析与思路

这道题目要求通过翻转数组中的一个子数组,并找到翻转后数组中的最大子数组和。让我们逐步分析解题思路:

1. 理解最大子数组和问题

最大子数组和问题是一个经典问题,通常使用 Kadane 算法 来求解。在没有翻转的情况下,我们可以直接通过 Kadane 算法计算出最大子数组和。

Kadane 算法的核心思想

  • 在遍历数组的过程中,维护一个变量 current_sum,表示当前子数组的和。
  • 如果当前元素加入到 current_sum 后仍然增大子数组和,则继续累加。如果加入后反而减少了子数组和,则从当前元素重新开始计算。
  • 同时维护一个变量 max_sum,记录遍历过程中得到的最大子数组和。

Kadane 算法的时间复杂度是 O(N),可以高效地解决原数组的最大子数组和。

2. 考虑翻转操作的影响

题目要求我们通过一次操作来翻转任意子数组,目的是找到可能得到的最大子数组和。翻转操作的本质是对数组进行局部逆序排列,可能会导致不同的子数组和,从而改变最大子数组和。

翻转子数组的过程会影响以下几个方面:

  • 子数组的和:翻转后,子数组的和会发生变化,可能原本和较小的子数组翻转后变大。
  • 整体子数组的和:翻转操作可能使得原本断裂的子数组合并,从而产生更大的子数组和。

因此,我们不仅要考虑原数组的最大子数组和,还需要考虑翻转后可能得到的不同子数组和。

3. 翻转子数组的具体实现

我们可以通过枚举所有可能的翻转子数组来解决这个问题。具体地:

  • 对于每一对 (left, right),我们可以翻转数组中 data_array[left]data_array[right] 之间的元素,然后计算翻转后的最大子数组和。
  • 计算翻转后的最大子数组和时,使用 Kadane 算法。

4. 优化方向与挑战

  • 由于每次翻转都会涉及到一个新的数组,我们可能需要 O(N^2) 的复杂度来遍历所有子数组的区间 (left, right)
  • 由于存在两种操作:不翻转翻转子数组,我们需要分别计算两种情况的最大子数组和,最终取二者的最大值。

代码详解

#include <vector>
#include <algorithm>

int kadane(const std::vector<int>& arr) {
    // Kadane 算法,计算最大子数组和
    int max_sum = arr[0], current_sum = arr[0];
    for (int i = 1; i < arr.size(); ++i) {
        current_sum = std::max(arr[i], current_sum + arr[i]);
        max_sum = std::max(max_sum, current_sum);
    }
    return max_sum;
}

int solution(int N, std::vector<int>& data_array) {
    // 1. 计算不翻转的最大子数组和
    int original_max_sum = kadane(data_array);

    // 2. 尝试翻转每一个子数组,计算翻转后的最大子数组和
    int max_flip_sum = original_max_sum;

    // 遍历所有可能的子数组 (left, right) 进行翻转
    for (int left = 0; left < N; ++left) {
        for (int right = left; right < N; ++right) {
            // 对子数组进行翻转
            std::vector<int> flipped = data_array;
            std::reverse(flipped.begin() + left, flipped.begin() + right + 1);
            // 计算翻转后的最大子数组和
            max_flip_sum = std::max(max_flip_sum, kadane(flipped));
        }
    }

    return max_flip_sum;
}

int main() {
    std::vector<int> array1 = {1, 2, 3, -1, 4};
    std::vector<int> array2 = {-85, -11, 92,  6,  49,  -76, 28,  -16, 3,   -29, 26,  37,  86,  3,   25, -43, -36, -27, 45, 87,  91,  58,  -15, 91,  5,   99,  40,  68,  54,  -95, 66,  49,  74,  9,  24,  -84, 12,  -23, -92, -47, 5,   91,  -79, 94,  61, -54, -71, -36, 31, 97,  64,  -14, -16, 48,  -79, -70, 54,  -94, 48,  37, 47,  -58, 6,   62, 19,  8,   32,  65,  -81, -27, 14,  -18, -34, -64, -97, -21, -76, 51,  0,  -79, -22, -78, -95, -90, 4,   82,  -79, -85, -64, -79, 63,  49,  21,  97, 47, 16, 61,  -46, 54,  44};

    std::cout << (solution(5, array1) == 10) << std::endl;
    std::cout << (solution(100, array2) == 1348) << std::endl;

    return 0;
}

  1. Kadane算法

    • 我们使用 Kadane 算法来计算最大子数组和。每遍历一个元素,更新 current_sum 为当前子数组和的最大值(可以选择继续加上当前元素或重新开始)。同时,维护全局最大和 max_sum
  2. 翻转子数组的计算

    • 我们通过两层循环遍历所有可能的子数组 (left, right),然后翻转这段子数组。
    • 计算翻转后的数组的最大子数组和,并更新 max_flip_sum
  3. 时间复杂度分析

    • Kadane 算法 的时间复杂度是 O(N),用于计算原数组和每个翻转后的数组的最大子数组和。
    • 我们有两层循环遍历每一对 (left, right),因此遍历的时间复杂度是 O(N^2)。
    • 因此,总时间复杂度是 O(N^3),对于较大的输入(例如 N 接近 1000 时),可能需要优化。

总结与优化建议

总结

  • 这道题考察了如何计算子数组和以及如何处理数组翻转对结果的影响。
  • 使用 Kadane 算法解决最大子数组和是一个高效的技巧,而翻转操作带来的最大子数组和问题则增加了复杂度。
  • 我们的实现通过暴力法枚举每个子数组区间,并对翻转后的数组进行 Kadane 算法求解,最终得出答案。

对入门同学的建议

  1. 学习 Kadane 算法:Kadane 算法是解决最大子数组和问题的基础,理解并掌握它对这类问题至关重要。
  2. 多练习数组操作与区间问题:这道题目涉及到数组的区间操作,掌握如何快速处理数组的子区间对于高效解决问题非常重要。
  3. 思考复杂度:在编写代码时,要考虑时间复杂度,尤其是在处理大数据时,暴力解法可能无法通过。了解如何优化算法非常关键。