在计算机科学中,最大子数列问题的目标是在数列的一维方向找到一个连续的子数列,使该子数列的和最大。举一个最简单的例子,给出数组{-4,2,-1,5}求解它的最大子列和。常规思路逐一寻找,具体步骤如下:
①寻找长度为1的子列:{-4},{2},{-1},{5}。数列和分别为:-4,2,-1,5
②寻找长度为2的子列:{-4,2},{2,-1},{-1,5}。数列和分别为:-2,1,4
③寻找长度为3的子列:{-4,2,-1},{2,-1,5}。数列和分别为:-3,6
④寻找长度为4的子列:{-4,2,-1,5}。数列和分别为:2
所以所给数组的最大子列和为:6
暴力求解算法就不多说了,下面给出两种复杂度较低的算法,分治法和在线处理法。注:算法具体思路借鉴浙大陈姥姥mooc讲解。
一、分治法
分支法的大致思想就是将大的问题分解成小的子问题,最后将子问题归纳最终得出最终解,时间复杂度为O(nlogn)。上述例子中,所给出的数组含有4个元素,按照分治思想,将{-4,2,-1,5}分解为{-4,2}和{-1,5}两个子数组,继续分解为{-4},{2},{-1},{5}四个数组分别求解。分解部分完成以后就需要按照要求最分解后的子数组进行合并。
①{-4,2,-1,5}。以中间轴(红色逗号)为分界向两边找4个部分的子列和,得到4个最佳子列(下划线)。它们的最大子列和为5。
②{-4,2,-1,5}。①中1,2部分合并,3,4部分合并,需要注意的是合并后保证以中间轴(红色逗号)为中心能取到最佳子列,最大子列和为4。
③{-4,2,-1,5}。对②中的部分再次进行合并,得到最大子列和为6。
算法如下:
///返回三个整数中的最大值
int Max3(int A,int B,int C){
return A>B?A>C?A:C:B>C?B:C;
}
///分治法求List[left]到List[right]的最大子列和
int DivideAndConquer(int List[],int left,int right){
int MaxLeftSum,MaxRightSum;///存放左右子问题的结
int MaxLeftBorderSum,MaxRightBorderSum;///存放跨分界的结果
int LeftBorderSum,RightBorderSum;///用于左右两边累加计算
///当子列中只有1个数字时,递归终止
if(left==right)
return List[left];
///分的过程:找到中分点
int center=(left+right)/2;
///递归求两边子列的最大和
MaxLeftSum=DivideAndConquer(List,left,center);
MaxRightSum=DivideAndConquer(List,center+1,right);
///求跨越分界的最大子列和
MaxLeftBorderSum=List[center];
LeftBorderSum=List[center];
///从中间向左扫描
for(int i=center+1;i>=left;i--){
LeftBorderSum+=List[i];
if(LeftBorderSum>MaxLeftBorderSum){
MaxLeftBorderSum=LeftBorderSum;
}
}
///从中间向右边扫描
MaxRightBorderSum=List[center+1];
RightBorderSum=List[center+1];
for(int i=center+2;i<=right;i++){
RightBorderSum+=List[i];
if(RightBorderSum>MaxRightBorderSum){
MaxRightBorderSum=RightBorderSum;
}
}
///返回治的结果
return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
}
///函数调用接口
int MaxSubseqSum3(int List[],int N){
return DivideAndConquer(List,0,N-1);
}
二、在线处理法(O(n))
在线处理法就是数组从左到右逐一添加元素加和,记录最大子列和,时间复杂度为O(n)。同样,以数组{-4,2,-1,5}进行分析。
设置ThisSum用于累加记录子列和,MaxSum记录当前最大子列和。初始化ThisSum=0,MaxSum=0
①ThisSum累加第1个元素,ThisSum=-4,小于零可以观察到,数组中存在大于零的元素,在这里ThisSum<0,ThisSum中加了负数会使子列和减小。显然不可能为最大子列和,此时无需更新MaxSum,且将ThisSum置0。
②ThisSum累加第2个元素,ThisSum=2,ThisSum>MaxSun=m,更新MaxSum=2。
③ThisSum累加第3个元素,ThisSum=ThisSum+(-1)=1,此时,ThisSum>0且ThisSum<MaxSum,无需操作。
④ThisSum累加第4各元素,ThisSum=ThisSum+5=6,此时,ThisSum>MaxSum,更新MaxSum=6。至此,数组元素全部访问结束,最终得到最大子列和为6。
算法如下:
///在线处理法求解最大子列和
int MaxSubseqSum4(int List[],int N){
int ThisSum=0,MaxSum=0;///ThisSum用于累加,MaxSum记录最大值
for(int i=0;i<N;i++){
ThisSum+=List[i];
if(ThisSum>MaxSum){
MaxSum=ThisSum;
}else if(ThisSum<0){
ThisSum=0;
}
}
return MaxSum;
}
细心的读者可能已经发现,如果数组元素全部为负数呢,因此,这里就会涉及到一个问题,在线处理法的有效性问题,以上述算法为例,在线处理法可以用来解决数组元素中含有非负值的情况,但当所有元素均为负数时,该算法将会失效,只能借助分治法进行求解。所以,在运用在线处理法时 ,需要首先判断一下数组元素中是否存在非负值,如果不存在,则无法使用在线处理法。
测试代码:
#include<bits/stdc++.h>
using namespace std;
///返回三个整数中的最大值
int Max3(int A,int B,int C){
return A>B?A>C?A:C:B>C?B:C;
}
///分治法求List[left]到List[right]的最大子列和
int DivideAndConquer(int List[],int left,int right){
int MaxLeftSum,MaxRightSum;///存放左右子问题的结
int MaxLeftBorderSum,MaxRightBorderSum;///存放跨分界的结果
int LeftBorderSum,RightBorderSum;///用于左右两边累加计算
///当子列中只有1个数字时,递归终止
if(left==right){
if(List[left]>0){
return List[left];
}else{
return 0;
}
}
///分的过程:找到中分点
int center=(left+right)/2;
///递归求两边子列的最大和
MaxLeftSum=DivideAndConquer(List,left,center);
MaxRightSum=DivideAndConquer(List,center+1,right);
///求跨越分界的最大子列和
MaxLeftBorderSum=0;
LeftBorderSum=0;
///从中间向左扫描
for(int i=center;i>=left;i--){
LeftBorderSum+=List[i];
if(LeftBorderSum>MaxLeftBorderSum){
MaxLeftBorderSum=LeftBorderSum;
}
}
///从中间向右边扫描
MaxRightBorderSum=0;
RightBorderSum=0;
for(int i=center+1;i<=right;i++){
RightBorderSum+=List[i];
if(RightBorderSum>MaxRightBorderSum){
MaxRightBorderSum=RightBorderSum;
}
}
///返回治的结果
return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
}
///函数调用接口
int MaxSubseqSum3(int List[],int N){
return DivideAndConquer(List,0,N-1);
}
///在线处理法求解最大子列和
int MaxSubseqSum4(int List[],int N){
int ThisSum=0,MaxSum=0;///ThisSum用于累加,MaxSum记录最大值
for(int i=0;i<N;i++){
ThisSum+=List[i];
if(ThisSum>MaxSum){
MaxSum=ThisSum;
}else if(ThisSum<0){
ThisSum=0;
}
}
return MaxSum;
}
int main(){
int s[8]={-1,3,-2,-4,-6,1,6,-1};
cout<<"分治法:"<<MaxSubseqSum3(s,8)<<endl;
cout<<"在线处理法:"<<MaxSubseqSum4(s,8);
return 0;
}
实战:53. 最大子数组和 - 力扣(LeetCode)https://leetcode.cn/problems/maximum-subarray/submissions/