问题描述
给定一个数组 arr,可以选择一个区间 [i, j],将该区间内的元素翻转(即变成相反数),然后重新计算最大子数组和。目标是找到一个最优的翻转操作,使得数组的最大子数组和达到最大值。
解题思路
-
最大子数组和的计算:
- 使用 Kadane 算法,线性时间内计算数组的最大子数组和。Kadane 算法通过维护两个变量
maxEndingHere和maxSoFar,动态更新当前的子数组和与全局最大值。
- 使用 Kadane 算法,线性时间内计算数组的最大子数组和。Kadane 算法通过维护两个变量
-
枚举所有翻转区间:
- 使用双层循环枚举所有可能的区间
[i, j],每次翻转区间后,重新计算翻转后的最大子数组和,保留全局最大值。
- 使用双层循环枚举所有可能的区间
-
翻转区间的实现:
- 为了避免直接修改原数组,使用一个辅助数组来存储翻转后的结果。
-
前缀和的优化:
- 构造前缀和数组,快速求得任意区间的和,便于验证翻转的效果。
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);
}
}
关键逻辑解析
-
Kadane算法:
- 如果没有翻转操作,直接调用
maxSubArraySum就可以得出最大子数组和。 - 时间复杂度为 (O(N))。
- 如果没有翻转操作,直接调用
-
翻转子数组:
- 双层循环枚举区间
[i, j],在每个区间上执行翻转操作。 - 翻转操作的时间复杂度为 (O(N)),双层循环使得枚举的复杂度为 (O(N^2))。
- 双层循环枚举区间
-
结合前缀和:
- 在翻转区间时,直接利用前缀和计算
[i, j]的和,并快速更新翻转后的数组。 - 但此实现中直接克隆了原数组,因此未完全优化。
- 在翻转区间时,直接利用前缀和计算
性能与优化
当前实现的时间复杂度为 (O(N^3)),因为:
- 双层循环的复杂度为 (O(N^2))。
- 每次翻转后需重新计算最大子数组和,复杂度为 (O(N))。
可以通过以下方式优化:
-
动态维护翻转增益:
- 利用 Kadane 算法的变种,动态记录翻转区间带来的增益,避免每次重新计算整个数组。
-
一次遍历更新翻转后的数组:
- 在翻转区间时,直接更新翻转后的最大子数组和,而不是完全重新计算。
测试用例与结果验证
对于数组 {1, 2, 3, -1, 4}:
- 初始最大子数组和为
9。 - 翻转区间
[3, 3]后,最大子数组和变为10。
对于数组 {-1, -2, -3, 4, 5}:
- 初始最大子数组和为
9。 - 不需要任何翻转,结果仍为
9。