Maximum Subarray
给定一个整型数组A[],找出数组中连续子串的最大和。
例如:给定数组【-2,1,-3,4,-1,2,1,-5,4】,和最大的连续子串为【4,-1,2,1】
2.题目分析:
题目只需要找到最大和,不需要返回连续子串。有两种思路如下:
解法1:用O(n)的方法,遍历一遍数组得到结果
【思路】:
首先联想到栈操作,可以遍历一遍数组进行栈操作,具体操作及触发条件如下:sum为已有和
1)入栈:如果sum+A[i]>0则新sum对于后面元素有意义的前缀,此时将A[i]入栈,更新sum值和max值。
2)出栈:如果sum+A[i]<=0则新sum对于后面元素无意义,应全部删除,则将此时栈中元素全部弹出,sum=0。(弹出的元素对max
的影响在之前步骤已经有考虑,不用担心)
另:注意严谨性。一方面,max初始化不应为0.而应该为A[i]。另一方面,单个值可能比和还大,所以在每一轮循环开始时都应该判
断,也解决了所有元素均为负的情况。
public int maxSubArray(int[] A) {
int max=A[0]; //注意要严谨,不能初始化为0
int sum=0;
for(int i=0;i<A.length;i++){
if(A[i]>max){ //注意严谨性,单个值可能比和还大,通过每一轮循环检测的方式解决
max=A[i];
}
if(sum+A[i]>0){ //为正才有继续下去的必要,否则作为左前缀可以直接去掉(联想清空栈)
sum+=A[i];
if(sum>max){
max=sum;
}
}else{
sum=0; //若为负则清零,这样也可保证第一个元素一定为正
}
}
return max;
}
解法2:动态规划
【思路】:
更加简单的思路,另开一个数组b[],b[i]表示以A[i]元素结尾的最大子串的和(注意“结尾”的含义),则遍历一遍A[]的时候求得b[],b[]中最大的元素即为所求。用数组的形式保存每一步的值,比直接调用递归更加高效,省去了中间大量的重复运算。
public int maxSubArray(int[] A) {
int max=A[0];
int b[]=new int[A.length];
b[0]=A[0]; //注意初始化
for(int i=1;i<A.length;i++){
b[i]=A[i]>(b[i-1]+A[i])?A[i]:(b[i-1]+A[i]); //以A[i]结尾则只需考虑两种情况
if(b[i]>max){
max=b[i];
}
}
return max;
}
解法3:Divide and Conquer分治
【思路】:
首先找到中间元素A[mid],有两种情况。其一,连续子数组包含中间元素,则最大和在左边数组或右边数组产生,递归即可。其二,连续子数组包含中间元素,则求得左边
数组以A[mid-1]为后缀的连续子数组最大和,求得右边以A[mid+1]为前缀的连续子数组最大和,再组合找到"左+中"、“中+右”、“左+中+右”三个中最大的,与第一种情况最大值比较
,综合最大的即为所求。
public class Solution {
public int max(int a,int b){
return a>b?a:b;
}
//分治的方法
public int msa(int[] A,int left,int right){
if(left==right){ //子数组只有一个元素,则最大连续子数组和就为那个元素
return A[left];
}
int mid=(left+right)/2; //找到中间元素下标
if(mid==left){ //只两个元素
return max(A[left],max(A[right],A[left]+A[right]));
}
int leftSum=msa(A,left,mid-1); //左边最大和,是不考虑后缀的情况
int rightSum=msa(A,mid+1,right); //右边最大和,是不考虑前缀的情况
//考虑左边后缀的情况
int sumL=A[mid-1];
int tmp=0;
for(int i=mid-1;i>=left;i--){ //一轮循环找到包含A[mid-1]的最大和
tmp+=A[i];
if(tmp>sumL){
sumL=tmp;
}
}
//考虑右边前缀的情况
int sumR=A[mid+1]; //一轮循环找到包含A[mid+1]的最大和
tmp=0;
for(int i=mid+1;i<=right;i++){
tmp+=A[i];
if(tmp>sumR){
sumR=tmp;
}
}
int sumNo=max(leftSum,rightSum); //不包mid
int sumYes1=max(A[mid],sumL+A[mid]+sumR);
int sumYes2=max(sumR+A[mid],sumL+A[mid]);
return max(sumNo,max(sumYes1,sumYes2));
}
//n为数组的长度
public int maxSubArray(int[] A) {
if(A.length==0){
return 0;
}else{
return msa(A,0,A.length-1);
}
}
}
3.扩展:打印最大和以及对应的连续子串。
若是解法1则在更新sum和max的同时更新子串起止下标即可。
若是解法2则traceback。
解法1:用O(n)的方法,遍历一遍数组得到结果
【思路】:
首先联想到栈操作,可以遍历一遍数组进行栈操作,具体操作及触发条件如下:sum为已有和
1)入栈:如果sum+A[i]>0则新sum对于后面元素有意义的前缀,此时将A[i]入栈,更新sum值和max值。
2)出栈:如果sum+A[i]<=0则新sum对于后面元素无意义,应全部删除,则将此时栈中元素全部弹出,sum=0。(弹出的元素对max
的影响在之前步骤已经有考虑,不用担心)
另:注意严谨性。一方面,max初始化不应为0.而应该为A[i]。另一方面,单个值可能比和还大,所以在每一轮循环开始时都应该判
断,也解决了所有元素均为负的情况。
public int maxSubArray(int[] A) {
int max=A[0]; //注意要严谨,不能初始化为0
int sum=0;
for(int i=0;i<A.length;i++){
if(A[i]>max){ //注意严谨性,单个值可能比和还大,通过每一轮循环检测的方式解决
max=A[i];
}
if(sum+A[i]>0){ //为正才有继续下去的必要,否则作为左前缀可以直接去掉(联想清空栈)
sum+=A[i];
if(sum>max){
max=sum;
}
}else{
sum=0; //若为负则清零,这样也可保证第一个元素一定为正
}
}
return max;
}
【思路】:
更加简单的思路,另开一个数组b[],b[i]表示以A[i]元素结尾的最大子串的和(注意“结尾”的含义),则遍历一遍A[]的时候求得b[],b[]中最大的元素即为所求。用数组的形式保存每一步的值,比直接调用递归更加高效,省去了中间大量的重复运算。
public int maxSubArray(int[] A) {
int max=A[0];
int b[]=new int[A.length];
b[0]=A[0]; //注意初始化
for(int i=1;i<A.length;i++){
b[i]=A[i]>(b[i-1]+A[i])?A[i]:(b[i-1]+A[i]); //以A[i]结尾则只需考虑两种情况
if(b[i]>max){
max=b[i];
}
}
return max;
}
【思路】:
首先找到中间元素A[mid],有两种情况。其一,连续子数组包含中间元素,则最大和在左边数组或右边数组产生,递归即可。其二,连续子数组包含中间元素,则求得左边
数组以A[mid-1]为后缀的连续子数组最大和,求得右边以A[mid+1]为前缀的连续子数组最大和,再组合找到"左+中"、“中+右”、“左+中+右”三个中最大的,与第一种情况最大值比较
,综合最大的即为所求。
public class Solution {
public int max(int a,int b){
return a>b?a:b;
}
//分治的方法
public int msa(int[] A,int left,int right){
if(left==right){ //子数组只有一个元素,则最大连续子数组和就为那个元素
return A[left];
}
int mid=(left+right)/2; //找到中间元素下标
if(mid==left){ //只两个元素
return max(A[left],max(A[right],A[left]+A[right]));
}
int leftSum=msa(A,left,mid-1); //左边最大和,是不考虑后缀的情况
int rightSum=msa(A,mid+1,right); //右边最大和,是不考虑前缀的情况
//考虑左边后缀的情况
int sumL=A[mid-1];
int tmp=0;
for(int i=mid-1;i>=left;i--){ //一轮循环找到包含A[mid-1]的最大和
tmp+=A[i];
if(tmp>sumL){
sumL=tmp;
}
}
//考虑右边前缀的情况
int sumR=A[mid+1]; //一轮循环找到包含A[mid+1]的最大和
tmp=0;
for(int i=mid+1;i<=right;i++){
tmp+=A[i];
if(tmp>sumR){
sumR=tmp;
}
}
int sumNo=max(leftSum,rightSum); //不包mid
int sumYes1=max(A[mid],sumL+A[mid]+sumR);
int sumYes2=max(sumR+A[mid],sumL+A[mid]);
return max(sumNo,max(sumYes1,sumYes2));
}
//n为数组的长度
public int maxSubArray(int[] A) {
if(A.length==0){
return 0;
}else{
return msa(A,0,A.length-1);
}
}
}
3.扩展:打印最大和以及对应的连续子串。
若是解法1则在更新sum和max的同时更新子串起止下标即可。
若是解法2则traceback。