问题描述:所谓的最大子数组,就是原数组中连续的数组元素之和最大的数组元素的集合。比如购买股票。如果把相邻两天的股价之差作为数组元素,那么求在连续的某个时间段内买入股票的最佳时间和卖出股票的最佳时间就可以抽象为计算最大子数组的问题。
举个例子
某公司股票的价值如下
第一天价值为10元,第二天11元,第三天7元,第四天10,第五天6元
10 11 7 10 6
最大收益的应该是在第3天买入,第五天卖出
这个问题可以简化为
第二天-第一天=1,第三天-第二天= -4,第四天-第三天=3,第五天-第四天= -4
1 -4 3 -4
最大的收益应该是3,为这个数组中的最大子数组。
1. 分治策略
假定我们要找子数组a[low,,,high]的最大子数组,使用分治法技术意味着我们要将子数组划分为两个规模尽量一样的子数组,也就是子数组的中间位置mid,然后考虑求解两个子数组a[low,,,mid],a[mid+1,,,high]。
a[low,,,high]的任何连续子数组a[i,,,j]所处的位置必然是以下三种情况之一
- 完全位于子数组a[low,mid]中
- 完全位于子数组a[mid+1,high]中
- 跨越了中点。
#define min -24551215
#include<stdio.h>
struct tuple{
int low;
int high;
int maxsum;
};
//定义一个结构体存放最大子数组在原数组的左下标右下标以及最大和
tuple findmaxcrossingsubarray(int a[100],int low,int mid,int high)
{
int leftsum=min;
int maxleft;
int rightsum=min;
int maxright;
int sum=0;
for(int i=mid;i>=low;i--)
{
sum=sum+a[i];
if(sum>leftsum)
{
leftsum=sum;
maxleft=i;
}
}
sum=0;
for(int i=mid+1;i<=high;i++)
{
sum=sum+a[i];
if(sum>rightsum)
{
rightsum=sum;
maxright=i;
}
}
tuple temp;
temp.low=maxleft;
temp.high=maxright;
temp.maxsum=leftsum+rightsum;
return temp;
}
tuple findmaximumsubarray(int a[100],int low,int high)
{
if(high==low)
{
tuple tuple0;
tuple0.low=low;
tuple0.high=high;
tuple0.maxsum=a[low];
return tuple0;
}
else
{
int mid=(low+high)/2;
tuple tuple1=findmaximumsubarray(a,low,mid);
tuple tuple2=findmaximumsubarray(a,mid+1,high);
tuple tuple3=findmaxcrossingsubarray(a,low,mid,high);
if((tuple1.maxsum>=tuple2.maxsum)&&(tuple1.maxsum>=tuple3.maxsum))
return tuple1;
else if((tuple2.maxsum>=tuple1.maxsum)&&(tuple2.maxsum>=tuple3.maxsum))
return tuple2;
else
return tuple3;
}
}
int main()
{
tuple result;
int x[18]={0,100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97};
int y[17]={0,13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
result=findmaximumsubarray(y,1,16);
printf("%d %d %d ",result.low,result.high,result.maxsum);
return 0;
}
可以参考下面的分治法解最大子数组问题
2.动态规划法
解析
-
状态定义:设置动态规划列表dp,dp【i】代表以元素nums【i】为结尾的连续子数组最大和
-
状态转移方程,如果dp【i-1】<=0,则dp【i-1】对dp【i】产生负影响,就是说dp【i-1】+nums【i】比nums【i】本身要小
- 当dp【i-1】大于零时,执行dp【i】=dp【i-1】+nums【i】;
- 当dp【i-1】小于等于零,执行dp【i】=nums【i】;
-
初始状态dp【0】=nums【0】,以nums【0】结尾的连续子数组最大和就是nums【0】
-
返回dp动态列表中的最大值。
空间复杂度降低:
由于 dp[i] 只与 dp[i−1] 和 nums[i]有关系,因此可以将原数组 nums 用作 dp 列表,即直接在 nums 上修改即可。
由于省去 dp 列表使用的额外空间,因此空间复杂度从 O(N) 降至 O(1) 。
java代码实现:
class Solution {
public int maxSubArray(int[] nums) {
int res = nums[0];
for(int i = 1; i < nums.length; i++) {
nums[i] += Math.max(nums[i - 1], 0);
res = Math.max(res, nums[i]);
}
return res;
}
}