vlambda博客
学习文章列表

算法练习-求最大子数组和


1 题目

1.1 题目描述

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

1.2 题目分析及解答

解题前,我们必须要知道什么是子数组?

子数组:一个或连续多个数组中的元素组成一个子数组(子数组至少包含一个元素);

思路一

我们对整个数组进行遍历,从数组第一个元素一一和后面的元素进行相加不断扩充序列,将结果值进行比较,直到得到第一轮的最大值,在从数组第二个元素一一和后面的元素进行相加不断扩充序列和第一轮的最大值进行比较,重复执行以上步骤直到最后一个元素,这样会得到最大子数组。

1.因为最大子数组 startIndex 和 endIndex 不确定,所以肯定需要for循环遍历来确定。
2.因为遍历所有元素作为 startIndex 需要一层for循环,另外遍历所有元素作为endInex也需要一层for循环。所以一定需要两层for循环。
3.计算 endIndex 思路:例如数组是[a, b, c, d, e],如果a + b > 当前最新的count,则更新 count;否则不更新,继续累加。
4.两层层遍历结束,最终的 count 就是最大的和。

    

算法练习-求最大子数组和


解题代码

package Array;

//三层循环,找出所有的子数组,并求出他们对应的和,再取最大值
public class MaxSubArray {
//求最大子数组之和
public static int maxSubArray(int array[]) {
int len = array.length;
int ThisSum = 0,MaxSum = 0,i,j,k;
for(i=0;i<len;i++)
for(j=i;j<len;j++) {
ThisSum = 0;
for(k=i;k<j;k++)
ThisSum += array[k];
if(ThisSum>MaxSum)
MaxSum = ThisSum;
}
return MaxSum;

}

public static void main(String[] args) {
int array[] = {-2,1,-3,4,-1,2,1,-5,4};
System.out.println("最大子数和为:"+maxSubArray(array));
}

}

该法分析:暴力求解虽然能得到结果,但是由于算法复杂度过高,对于数据量比较大的情况下,时间会更长。

暴力搜索的实现方法还有更简单的写法(算法笔试适用,场景中要考虑全面些

package array;
import java.util.Arrays;

public class IdeaOne {

public static void main(String[] args) {
//数组可以随机定义,此处选取LeetCode数据
int[] array = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(solution(array));
}

public static int solution(int[] array) {
int startIndex = 0;
int endIndex = 0;
int count = 0;
//第一层遍历:所有元素作为 startIndex
for (int i = 0; i < array.length; i++) {
// 注意1:一定需要一个 tempCount 来一直做累加,然后把最终值赋给count
int tempCount = 0;
for (int j = i; j < array.length; j++) {
//第二层遍历:计算 i 作为 startIndex 时,哪个元素为和最大的的 endIndex
tempCount += array[j];
if (tempCount > count) {
//注意2:此处有个关键代码[ tempCount > count ],通过此条件更新最终结果
startIndex = i;
endIndex = j;
count = tempCount;
}
}
}
return count;
}
}

思路二

对于一个数组元素进行遍历,遍历依然是从左到右,但是累加判断分拆为两个方向。假设遍历到元素:x,y

1.判断条件 x+y 后和 是否大于当前的最大值,如果大于当前的最大值则更新最大值

2.判断条件 x+y 之后的和是否小于 y,如果小于y,代表b之前的元素之和是负数,需要抛弃,y为最新的开始索引下标值。

3.累加的判断拆分为以上两点,但是写代码时要先 判断第一个条件,再判断第二个条件,因为更新累加和的逻辑一定在代码块的末尾位置。

算法练习-求最大子数组和

解题代码

package array;

public class IdeaOne{
public static void main(String[] args){
//数组可以随机定义,此处选取LeetCode数据
int[] array = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(solution(array));

}
public static int solution(int[] arr) {
int max=0;
int thisMax=0;
for (int i=0;i<arr.length;i++) {
thisMax+=arr[i];
//若thisMax有没有令arr[i]变小,则从arr[i]重新开始累计
if (thisMax<arr[i]) {
thisMax=arr[i];
}
//若当前的thisMax已经比上一个max大,则将thisMax记为max
if (thisMax>max) {
max=thisMax;
}
}
//System.out.println(max);
return max;
}

}

同理,该方法相比较暴力搜索算法:利用已计算的的子数组和。假设Sum[ i , j ] = Sum [ i , j - 1 ] + arr[ j ] ,则采用这种方式可以省去计算 Sum [ i , j - 1 ] 的时间。

public static int maxSubArray(int array[]) {
int len = array.length;
int maxSum = Integer.MIN_VALUE;
for(int i=0;i<len;i++) {
int sum = 0;
for(int j=i;j<len;j++) {
sum += array[j];
if(sum>maxSum) {
maxSum = sum;
}
}
}
return maxSum;
}

public static void main(String[] args) {
int array[] = {-2,1,-3,4,-1,2,1,-5,4};
System.out.println("最大子数组之和:"+maxSubArray(array));
}

思路三

根据数组的最后一个元素arr[ n - 1 ] 与最大子数组的关系分为一下三种:

1.最大子数组包含arr [ n - 1 ],即 arr [ n - 1 ] 结尾。

2.arr [ n - 1 ] 单独构成最大子数组。

3.最大子数组不包含 arr [ n - 1 ] ,那么求 arr [ 1 , … , n - 1 ] 的最大子数组可以转化成为求arr [ 1 , … , n - 2]的最大子数组。

注意:该方法是当前常用的动态规划方法。可以重点学习动态规划的思想。如下leetcode注解:

算法练习-求最大子数组和

算法练习-求最大子数组和


解题代码

package array;

public class IdeaThree {

public static void main(String[] args) {
int array[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println("最大子数组和:"+maxSubArray(array));
}


public static int max(int m,int n ) {
return m>n ? m:n;
}
public static int maxSubArray(int array[]) {
int n = array.length;
int End[] = new int[n];
int All[] = new int[n];
End[n - 1] = array[n - 1];
All[n - 1] = array[n - 1];
End[0] = All[0] = array[0];
for (int i = 1; i < n; i++) {
End[i] = max(End[i - 1] + array[i], array[i]);
All[i] = max(End[i], All[i - 1]);
}
return All[n - 1];
}
}


如何查看代码执行时间?

    为获得代码执行时间,大家可以参考下述代码(执行结果如下图):

package array;
public class Max_num_sub{
public static void main(String[] args) { //数组可以随机定义,此处选取LeetCode数据 int[] array = {-2,1,-3,4,-1,2,1,-5,4}; Long begintime = System.nanoTime(); int result = FindGreatestSumOfSubArray(array); Long endtime = System.nanoTime(); System.out.println("连续子数组的最大和为:"+result+",运行时间:"+(endtime - begintime) + "ns"); }
public static int FindGreatestSumOfSubArray(int[] array) { int len = array.length; if (len == 0){ return 0; } int currentsum = array[0]; int greatsetsum = array[0]; //System.out.println("第1步:累加子数组和:"+currentsum+",最大子数组和:"+greatsetsum); for(int i=1;i<array.length;i++){ if(currentsum > 0){ currentsum += array[i]; }else{ currentsum = array[i]; } if(currentsum > greatsetsum){ greatsetsum = currentsum; } //System.out.println("第"+(i+1)+"步:累加子数组和:"+currentsum+",最大子数组和:"+greatsetsum); } return greatsetsum; }
}