题解:数组翻转后最大子数组和问题
题目描述
给定一个数组,我们可以选择翻转数组中的任意一个子数组(可以不翻转)。目标是找到经过一次翻转操作后,数组中最大子数组和的可能最大值。子数组是数组中的连续部分,最大子数组和的定义是子数组中所有元素的和的最大值。
解题思路
这个问题可以分为两部分来解决:
- 不翻转子数组时的最大子数组和。
- 翻转子数组后的最大子数组和。
我们可以通过以下几个关键步骤解决该问题:
1. 不翻转时的最大子数组和
这是经典的最大子数组和问题,可以通过 Kadane算法 来解决。Kadane算法的核心思想是用动态规划来记录到当前位置的子数组最大和,并在每一步更新全局最大值:
- 定义
currentSum表示以当前元素为结束的子数组的最大和。 - 如果当前元素单独作为一个新的子数组的和更大,则舍弃之前的累积和,重新开始。
- 定义
maxSum表示全局最大子数组和,随着遍历更新该值。
算法时间复杂度为 O(N)O(N)O(N)。
2. 考虑翻转操作的情况
翻转操作会改变数组的某些部分,但我们可以将其效果分解为“消极部分变积极,积极部分变消极”。因此,翻转一个子数组的效果是:
- 翻转后的新数组和 = 原始数组和 + 2 × 翻转部分的和(取负数的贡献)。
我们通过遍历数组的所有可能子数组,计算翻转后对整体数组的影响:
- 计算子数组
[i, j]的和(通过前缀和快速实现)。 - 翻转
[i, j]后,其负数贡献是-sum[i..j]。 - 更新翻转后的新数组中的最大子数组和。
为了保证结果的正确性,翻转后的数组也需要重新计算其最大子数组和。这部分仍然用 Kadane算法来完成。
3. 遍历所有可能翻转的子数组
为了找到可能的最大值,我们需要尝试翻转数组的所有连续子数组 [i, j],并对每种情况重新计算最大子数组和:
- 使用双重循环遍历所有
[i, j]。 - 翻转
[i, j]后,重新生成数组并计算其最大子数组和。 - 用一个全局变量记录翻转后的最大值。
时间复杂度分析
-
Kadane算法:
- 找到原始数组的最大子数组和,时间复杂度为 O(N)O(N)O(N)。
-
翻转子数组:
- 双重循环遍历所有可能的子数组
[i, j],复杂度为 O(N2)O(N^2)O(N2)。 - 每次翻转后使用 Kadane 计算最大子数组和,复杂度为 O(N)O(N)O(N)。
- 总体复杂度为 O(N3)O(N^3)O(N3)。
- 双重循环遍历所有可能的子数组
虽然时间复杂度较高,但对于中等规模的 N≤100N \leq 100N≤100 问题,仍可以接受。
算法实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
// 使用Kadane算法找到最大子数组和
int kadane(const vector<int>& arr) {
int maxSum = INT_MIN, currentSum = 0;
for (int num : arr) {
currentSum = max(num, currentSum + num);
maxSum = max(maxSum, currentSum);
}
return maxSum;
}
int solution(int N, vector<int>& data_array) {
// 原数组的最大子数组和
int originalMaxSum = kadane(data_array);
// 初始化翻转后的最大值
int maxSumAfterReverse = originalMaxSum;
// 尝试翻转每个子数组
for (int i = 0; i < N; ++i) {
for (int j = i; j < N; ++j) {
// 翻转子数组 [i, j],并计算翻转后的数组
vector<int> temp = data_array;
// 反转子数组
reverse(temp.begin() + i, temp.begin() + j + 1);
// 在翻转后的数组上重新计算最大子数组和
int newMaxSum = kadane(temp);
// 更新最大值
maxSumAfterReverse = max(maxSumAfterReverse, newMaxSum);
}
}
return maxSumAfterReverse;
}
int main() {
vector<int> array1 = {1, 2, 3, -1, 4};
cout << (solution(5, array1) == 10) << endl;
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};
cout << (solution(100, array2) == 1348) << endl;
return 0;
}
总结
- Kadane算法:解决最大子数组和问题的高效工具,复杂度 O(N)O(N)O(N)。
- 翻转子数组的关键思路:翻转子数组改变其贡献,可以通过遍历子数组并重新计算来找到全局最优解。
- 优化空间:当前实现是暴力枚举翻转所有可能的子数组,复杂度为 O(N3)O(N^3)O(N3)。可以通过空间优化将时间复杂度降至 O(N2)O(N^2)O(N2),利用动态规划记录翻转对其他部分的影响。
本题的难点在于翻转对全局最大子数组和的影响分析。通过分治思想与算法优化,我们可以清晰地解决这个问题。