子数组的最大和[算法]HDU1003/HDU1231/找到这些数使得它满足:它是左边的最大值且是右边的最小值

题目

输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18。

要求最大的子数组的和.我们可以想象,当遍历到其中一个数A[i],如果A[i]之前的数组(当前之和)CurrentSum => 0,那么有CurrentSum+A[i]=>A[i],则CurrentSum应该继续累加CurrentSum+=A[i].如果CurrentSum < 0,那么有CurrentSum+A[i]<A[i],那么用CurrentSum+A[i]去更新CurrentSum的结果会比用A[i]去更新CurrentSum的结果更小,于是可以用A[i]去更新CurrentSum.用Sum来记录最终的最大值.如果CurrentSum > Sum,则用CurrentSum更新Sum.

代码:

#include<iostream>
#include<Limits.h>
using namespace std;
int MaxArray(int* A,int n)
{
	int Sum=INT_MIN;
	int CurrentSum=0;
	for(int i=0;i<n;++i)
	{
		if(CurrentSum < 0)//如果i之前的和为负,则抛弃前边的,从A[i]开始统计新的和
			CurrentSum = A[i];
		else			//如果i之前的为正,则继续累加
			CurrentSum += A[i];
		if(Sum < CurrentSum)
			Sum=CurrentSum;
	}
	return Sum;
}
void main()
{
	int A[]={ -2,11, 11, 9, -7, -2, -5,-1};
	int len=sizeof(A)/sizeof(A[0]);
	cout<<MaxArray(A,len);
}

实训演练http://acm.hdu.edu.cn/showproblem.php?pid=1003

这道题除了要求求出最小和之外,还需要求出最小数组的起始位置,一样可以类比,curSum 和Sum,可以想到用curBeg,curEnd, 和beg,end来实现。

//http://acm.hdu.edu.cn/showproblem.php?pid=1003
#include<stdio.h>
#include<limits.h>
void MaxSum(int* A,int n,int& max,int& beg,int& end)
{
	int curSum=0;
	int curBeg,curEnd;
	max= INT_MIN;
	curEnd=curBeg=beg=1;
	for(int i=0;i<n;++i)
	{
		if( curSum > 0) //if(curSum > 0) curSum + A[i] > A[i]
		{
			curSum += A[i];
		}
		else   // curSum + A[i] < A[i],用A[i]更新curSum
		{
			if( curSum < 0) //if (curSum == 0)不需要更新curBeg
				curBeg = i+1;
			curSum = A[i];	
		}
		if( curSum > max )
		{
			max = curSum ;
			beg = curBeg ;
			end = i+1;
		}
	}
}
int main()
{
	int A[100000];
	int n ,ArrayNum,max,beg,end;
	scanf("%d",&n) ;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&ArrayNum);
		for(int j=0;j<ArrayNum;++j)
			scanf("%d",&A[j]);
		printf("Case %d:\n",i);
		MaxSum(A,ArrayNum,max,beg,end);
		printf("%d %d %d\n",max,beg,end);
		if( i != n)
			printf("\n");
	}
	return 0;
}

测试用例:(全非负,全负,有正有负)

input

40 0 2 0 
6 2 7 -9 5 4 3 
4 0 0 -1 0 
7 -1 -2 -3 -2 -5 -1 -2 
6 -1 -2 -3 1 2 3 
5 -3 -2 -1 -2 -3

output

21 3

121 6

01 1

-11 1

64 6

-13 3

202 6
6 -1 2 3 4 5 6

另一道OJ题目:http://acm.hdu.edu.cn/showproblem.php?pid=1231

#include<stdio.h>
#include<limits.h>

int A[10005],K;
int LongSubArr(int& sum,int& beg,int& end)
{
    sum = INT_MIN;
    int i,cursum = 0,curbeg=0;
    for(i=0; i<K; ++i)
    {
        if( cursum < 0 )
        {
            cursum = A[i];
            curbeg = i;
        }
        else
            cursum += A[i];
        if( cursum > sum )
        {
            sum = cursum;
            beg = curbeg;
            end = i;
        }
    }
    return sum;
}
int main()
{
    int i,sum,beg,end;
    while(scanf("%d",&K)!=EOF && K !=0 )
    {
        for(i=0; i<K; ++i)
            scanf("%d",&A[i]);
        LongSubArr(sum,beg,end);
        if( sum < 0 )
            printf("0 %d %d\n",A[0],A[K-1]);
        else
            printf("%d %d %d\n",sum,A[beg],A[end]);
    }
}

面试的一道经典数组题目:在一个数组中,找到满足以下条件的所有这些数字,使得它比数组左边的数字都大,

同时比数组右边的数字都小.例如:A[]={1,4,3,7,8,11,65},1,7,8,11,65都是满足条件的数字,因为它们都比左边

的数字大比右边的数字小.这道题也有很简单的思路,蛮力法就可以轻松解决,遍历到的每一个数字,只需要判断

它的左边是否都比它小,它的右边都比它大,然后输出所有满足条件的数字即可,时间复杂度显然是O(n^2).但是这

种办法显然达不到面试官的要求,肯定有时间复杂度更低的算法.用DP思想,我们假定遍历到数组第k位时,第k位之前

(包括k)的最大值是j,表示为DP[k]=j,即k位之前的最大值为j.接下来的任务就是找初始状态和状态转移式(DP的两个基本点)

初态:DP[0]=A[0](显然成立)

转移式:DP[k]=max{ DP[k-1],A[k] }.

有了这两点,就可以构造DP[]数组(LeftMax)了.用同样的办法也可以构造出右边的最小值DP数组(RightMin).然后接下来

的工作就是比较LeftMax和RightMin数组对应下标的值是否相同,如果相同,说明这个值既是左边的最大值,又是右边的最小值,

就是我们要求的结果.其实,LeftMax和RightMin只需要求出其中一个即可,另一个无须保存,只需要用o(1)的一个变量存储即可.

//YY二面数组找一个值比左边的都大,比右边的都小.
#include<stdio.h>
#include<limits.h>

int RightMin[100],A[100]={1,4,3,7,8,11,65};

void Find(int n)
{
	int i;
	RightMin[n-1]=A[n-1];
	for(i=n-2;i>=0;--i)
	{
		if( A[i] < RightMin[i+1])
			RightMin[i]=A[i];
		else RightMin[i]=RightMin[i+1];
	}
	int LeftMax=INT_MIN;
	for(i=0;i<n;++i)
	{
		if( A[i] > LeftMax)
			LeftMax=A[i];
		if( LeftMax == RightMin[i])
			printf("%d ",A[i]);
	}
}
int main()
{
	Find(7);
	return 0;
}


你可能感兴趣的:(算法,子数组的最大和)