题目描述
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).
输入:[1,-2,3,10,-4,7,2,-5]
返回值:18
说明:输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18。
动态规划
使用动态规划来求解,需要一个表格数组dp
存储累加和,以及一个变量res
存储最优值,从头遍历数组,即从子问题开始,不断将问题规模扩大,同时利用已经求解的子问题来求解新的问题。
当子问题的累加和dp[i-1]
加上当前数组的值arr[i]
小于当前数组值arr[i]
时,说明之前的累加和为负数了,则没必要与之前的子数组继续累加,因此当以arr[i]
结尾时,累加和为dp[i]=arr[i]
,即抛弃之前的累加和,重新开始累加;反之dp[i-1] + arr[i] > arr[i]
则表明之前的累加和不是负数,可以继续累加,但是现在不一定是累加和最大的时候,因为arr[i]
可能是个负数,但是它后面可能存在更大的正数,我们不能就从这里断开了,还需要继续累加,所以累加完后要判断当前的累加和是否比历史的最优值res
更大,是则更新。
扩展,在动态规划的同时记录累加和最大的连续子数组
我们可以使用一个数组record
来记录最优值的解,即最大连续子数组,用-1
代表当前位置为连续子数组的开头,并且用一个变量max_idx
来记录最大连续子数组的结尾,当求解完后只需从max_idx
开始往回找到第一个-1
的位置即可找到最优连续子数组。record
的更新方法是:当dp[i-1]<0
时,表明前面的累加和是负数,此时没必要继续累加,重新开一个子数组,此时record[i] = -1
,否则继续累加,record[i] = 1
。并且更新最优解res
时,顺便更新最优子数组的结尾下表max_idx
为当前位置
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int len = array.length, res;
int[] dp = new int[len];
int[] record = new int[len];
res = dp[0] = array[0];
record[0] = -1; // 表示最大子数组从-1的地方开始
int max_idx = 0; // 记录最大连续子数组的结尾下标
for (int i = 1; i < len; i++) {
if (dp[i - 1] < 0) {
// 前面的小于0 抛弃掉
dp[i] = array[i];
record[i] = -1; // 重新开始连续子数组
} else {
// 可以继续累加
dp[i] = dp[i - 1] + array[i];
record[i] = 1;
}
// 与历史最好的比较
if (dp[i] > res) {
// 如果比历史值更好, 则更新历史最好值, 并记录下标位置
res = dp[i];
max_idx = i;
}
}
// 输出最好结果
int tmp_idx = max_idx;
while (tmp_idx > 0 && record[tmp_idx] != -1)
tmp_idx--;
for (int i = tmp_idx; i <= max_idx; i++)
System.out.print(array[i] + " ");
return res;
}
public static void main(String[] args) {
int[] arr = {1, -2, 3, 10, -4, 7, 2, -5};
new Solution().FindGreatestSumOfSubArray(arr);
}
}
数组法
这种方法其实和DP是一样的,只是它不需要类似DP的填表过程,因为这题没有要求记录最大和的解(即由哪几个数组成最大子数组),所以不填表也可以。同样使用res
来记录到当前为止最大的连续子数组的和,使用acc_sum
来记录到当前为止能累积的数组的和(不一定是最大的,只是说明能继续累积下去的数组的和),如果acc_sum<0
,则可以抛弃掉,因为它是负数了,没必要再加上它,让acc_sum
等于当前位置的数重新开始累积,否则acc_sum
还可以继续累加下去,即使当前位置的数是一个负数也可以累加,因为这个数的后面可能是一个更大的正数,所以前面说了acc_sum
并不一定是最大的。如果acc_sum>res
,则更新res
,说明累加到现在有更好的值。
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int len = array.length, res = array[0];
int acc_sum = array[0];
for (int i = 1; i < len; i++) {
if (acc_sum < 0)
// 重新开始
acc_sum = array[i];
else
// 可以继续累加
acc_sum += array[i];
if (acc_sum > res)
res = acc_sum;
}
return res;
}
}