【剑指Offer】连续子数组的最大和

来源:互联网 发布:java递归创建二叉树 编辑:程序博客网 时间:2024/05/29 17:22

题目:输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)

例如输入的数组为{1, -2, 3, 10, -4, 7, 2, -5}

看到题目,很多人都能想到最直观的方法,即枚举出数组的所有子数组并求出它们的和。一个长度为n的数组,总共有(n*(n+1))/2个子数组。计算出所有的子数组的和,最快也要O(n^2)的时间。通常最直观的方法不会是最优的方法,面试官将提示我们还有更快的方法。

 

解法一:举例分析数组的规律

我们试着从头到尾逐个累加示例数组中的每个数字,初始化和为0,第一步,加上第一个数字,此时和为1。接下来第二步加上数字-2,和就变成了-1。第三步加上数字3,我们注意到由于此前累加的和为-1,小于0,那如果用-13,得到的和为2,比3本身还小。也就是说从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。因此我们不用考虑从第一个数字开始的子数组,之前累加的和也被抛弃。

我们从第三个数字重新开始累加,此时得到的和为3。接下来第四步加10,得到的和为13。第五步加上-4,和为9。我们发现-4是一个负数,因此累加-4之后得到的和比原来的和还要小。因此我们要把之前得到的和13保存下来,它有可能是最大的子数组的和。第六步加上数字797的结果是16,此时和比之前最大的和13要大,把最大的子数组的和由13更新为16。第七步加上2,累加得到的和为18,同时我们也要更新最大子数组的和。第八步加上最后一个数字-5,由于得到结果为13,小于此前得到的最大值和18,因此最终最大的子数组的和为18,对应的子数组是{3, 10, -4, 7, 2}

public class GreatestSubArray {public int findGreatestSubArray(int[] array) {if(array == null || array.length == 0)return 0;int max = array[0];int sum = array[0];for(int i = 1; i < array.length; i++) {if(sum <= 0) {sum = array[i];} else {sum += array[i];}if(max < sum) {max = sum;}}return max;}}

解法二:动态规划

如果用函数f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求出max[f(i)],其中0in。我们能够使用下面的递归公式求f(i)

i0或者f(i-1)0时,f(i)=pData[i]

i0并且f(i-1)0时,f(i)=f(i-1) + pData[i]

这个公式的意义在于:当以第i-1个数字结尾的子数组中所有的数字的和小于0时,如果把这个负数与第i个数累加,得到的结果比第i个数字本身还要小,所以这种情况下以第i个数字结尾的子数组就是第i个数字本身。如果以第i-1个数字结尾的子数组中所有的数字的和大于0,与第i个数字累加就得到以第i个数字结尾的子数组中所有的数字的和。

public int findGreatestSumOfSubarray(int[] array) {if(array == null || array.length <= 0)return 0;int[] c = new int[array.length];//用来记录以当前元素结尾的子数组的最大和int max = array[0];int start = 0;//记录子数组的最大和的开始位置int end = 0;//记录子数组的最大和的结束位置int tmp = 0;c[0] = array[0];for(int i = 1; i < array.length; i++) {if(c[i-1] > 0) {c[i] = c[i-1] + array[i];} else {c[i] = array[i];tmp = i;}if(c[i] > max) {max = c[i];start = tmp;end = i;}}return max;}





原创粉丝点击