输入一个整型数组,数组中有正数也有负数。数组中的一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。
如输入{1,-2,3,10,-4,7,2,-5},和最大的子数组为{3,10,-4,7,2},输出应该是18
这是在《剑指offer》上看到的题目,这道题可以在O(n)的时间复杂度内求解,而且这个问题可以是很多更加复杂问题的子问题。所以记录加深下印象。
分析:
以上面列出的{1,-2,3,10,-4,7,2,-5}来分析,可以这么想,从头开始遍历,当前面某几项的和是负数,需要舍弃前面的。
即从1开始,此时记录到的最大和为1,然后遍历到-2,因为1+(-2)<0,所以舍弃掉-2,然后继续向后分析,3>1,所以更新最大值为3,然后继续向后遍历,3+10=13,更新最大值为13,13+(-4)=9,继续向后遍历。。。
即用两个中间变量来存储,一个用来存储遍历到当前项时最大的连续子数组之和(maxSum),另外一个用来存储包含当前项的最大子数组之和(curSum),如果curSum>maxSum,那么用curSum更新maxSum,否则继续遍历。
之前还没注意,后来看了书之后发现可以用动态规划来分析这个问题:
设f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求的是max{f(i)},0<=i<length,f(i)可以根据下面的公式求解:
f(i)=datas[i] i=0或者f(i-1)<=0
f(i)=f(i-1)+datas[i] i!=0或者f(i-1)>0
这个公式的含义是:当以第i-1个数字结尾的子数组中所有的数字的和小于0时,如果把这个负数与第i个数累加,得到的结果比第i个数字本身小,所以这种情况下以第i个数字结尾的子数组就是第i个数字本身。如果以第i-1个数字结尾的子数组中所有数组的和大于0,与第i个数字累加就得到以第i个数字结尾的子数组中所有数字的和。
这实际上可以当做上面分析的依据,实际上用递归来分析动态规划问题比较方便,但是编码还是基于循环来实现,就是上面的代码,其中f(i)就是curSum,max{f(i)}就是maxSum。
之前说过连续子数组的最大和可以是其它问题的子问题。下面是以前笔试遇到的问题:
假设已知一段时间内的股票价格走势,那么如果在这段时间内只可以买卖两次,并且只有当一笔交易完成后才可以做下一笔交易,那么最大收益是多少?
假定股票价格的走势以数组的形式给出:[1,4,5,2,3,1],表示第一天股票价格是1,第二天是4,第3天是5,第四天是2...(这里不考虑股票交易日限制的问题)。那么这两笔买卖可以第一天买,第二天卖,然后再第4天买,第5天买,这样第一笔收益是4-1=3,第二笔买卖收益是3-2=1.
这个问题实际上就是连续数组的最大和问题。先考虑只能进行一笔买卖的问题,1 ,4 ,5 ,2,3, 1之间的差值是3,1,-3,1,-2,对这个差值数组求最大子数组的和就是这一笔买卖最大的收益。即上面连续字数组的最大和问题。
然后考虑两笔买卖的问题,可以以上面的任意一天为分割点,将它分成两个时间段,然后分别求两个之间段的最大值,然后将这两个最大值相加。求出所有分割点的最大值后再进行比较,求出一个总的最大值。
下面是代码:
如输入{1,-2,3,10,-4,7,2,-5},和最大的子数组为{3,10,-4,7,2},输出应该是18
这是在《剑指offer》上看到的题目,这道题可以在O(n)的时间复杂度内求解,而且这个问题可以是很多更加复杂问题的子问题。所以记录加深下印象。
分析:
以上面列出的{1,-2,3,10,-4,7,2,-5}来分析,可以这么想,从头开始遍历,当前面某几项的和是负数,需要舍弃前面的。
即从1开始,此时记录到的最大和为1,然后遍历到-2,因为1+(-2)<0,所以舍弃掉-2,然后继续向后分析,3>1,所以更新最大值为3,然后继续向后遍历,3+10=13,更新最大值为13,13+(-4)=9,继续向后遍历。。。
即用两个中间变量来存储,一个用来存储遍历到当前项时最大的连续子数组之和(maxSum),另外一个用来存储包含当前项的最大子数组之和(curSum),如果curSum>maxSum,那么用curSum更新maxSum,否则继续遍历。
代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
//输入是否正确的标示符
bool f_validate=true;
int getGreatestOfSubArray(int *datas,int length)
{
if(datas==NULL|| length<=0)
{
f_validate=false;
return 0;
}
int curSum=0;
int maxSum=0x80000000;//0x80000000是最小的负整数
for(int i=0;i<length;i++)
{
//如果当前和小于等于0,那么舍弃掉它,从数组中的当前项开始累加
if(curSum<=0)
{
curSum=datas[i];
}else//否则当前和需要加上数组中的当前的项
{
curSum+=datas[i];
}
//每次计算一次当前的和后都需要更新最大和
if(curSum>maxSum)
maxSum=curSum;
}
return maxSum;
}
int main(){
int datas[]={1,-2,3,10,-4,7,2,-5};
int max;max=getGreatestOfSubArray(datas,sizeof(datas)/sizeof(datas[0]));
std::cout<<max<<std::endl;
return 0;
}
之前还没注意,后来看了书之后发现可以用动态规划来分析这个问题:
设f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求的是max{f(i)},0<=i<length,f(i)可以根据下面的公式求解:
f(i)=datas[i] i=0或者f(i-1)<=0
f(i)=f(i-1)+datas[i] i!=0或者f(i-1)>0
这个公式的含义是:当以第i-1个数字结尾的子数组中所有的数字的和小于0时,如果把这个负数与第i个数累加,得到的结果比第i个数字本身小,所以这种情况下以第i个数字结尾的子数组就是第i个数字本身。如果以第i-1个数字结尾的子数组中所有数组的和大于0,与第i个数字累加就得到以第i个数字结尾的子数组中所有数字的和。
这实际上可以当做上面分析的依据,实际上用递归来分析动态规划问题比较方便,但是编码还是基于循环来实现,就是上面的代码,其中f(i)就是curSum,max{f(i)}就是maxSum。
之前说过连续子数组的最大和可以是其它问题的子问题。下面是以前笔试遇到的问题:
假设已知一段时间内的股票价格走势,那么如果在这段时间内只可以买卖两次,并且只有当一笔交易完成后才可以做下一笔交易,那么最大收益是多少?
假定股票价格的走势以数组的形式给出:[1,4,5,2,3,1],表示第一天股票价格是1,第二天是4,第3天是5,第四天是2...(这里不考虑股票交易日限制的问题)。那么这两笔买卖可以第一天买,第二天卖,然后再第4天买,第5天买,这样第一笔收益是4-1=3,第二笔买卖收益是3-2=1.
这个问题实际上就是连续数组的最大和问题。先考虑只能进行一笔买卖的问题,1 ,4 ,5 ,2,3, 1之间的差值是3,1,-3,1,-2,对这个差值数组求最大子数组的和就是这一笔买卖最大的收益。即上面连续字数组的最大和问题。
然后考虑两笔买卖的问题,可以以上面的任意一天为分割点,将它分成两个时间段,然后分别求两个之间段的最大值,然后将这两个最大值相加。求出所有分割点的最大值后再进行比较,求出一个总的最大值。
下面是代码:
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
/*计算从first到last,包括first和last之间的差的最大和*/
int getGreatest(int *datas,int first,int last);
int getGreatestSubOfArray(int * datas,int length)
{
if(datas==NULL||length<=0) return -1;
int curSum=0;
int maxSum=0;
//分割,将数组以一个基准点分割成两部分,分别求两个部分的最大和,然后再求总的最大和
for(int i=1;i<length-2;i++)
{
curSum=getGreatest(datas,0,i)+getGreatest(datas,i+1,length-1);
if(curSum>maxSum)
{
maxSum=curSum;
}
}
return maxSum;
}
/*计算从first到last,包括first和last之间的差的最大和*/
int getGreatest(int *datas,int first,int last)
{
if(datas==NULL||first<0||last<0||first>=last) return -1;
int curSum=0;
int maxSum=0;
for(int i=first;i<last;i++)//从first到last依次遍历,计算相邻两项之间的差值,转换成了连续数组的最大和问题
{
if(curSum<=0)//如果当前和小于0,丢弃它
{
curSum=datas[i+1]-datas[i];
}else//否则加上当前项
{
curSum+=datas[i+1]-datas[i];
}
if(curSum>maxSum)//更新最大值
{
maxSum=curSum;
}
}
return maxSum;
}
int main(){
int datas[]={1,4,5,2,3,1};
int max=getGreatestSubOfArray(datas,sizeof(datas)/sizeof(datas[0]));
std::cout<<max<<std::endl;
return 0;
}