动态规划算法(连续子数组最大和,O(N)时间复杂度O(1)空间复杂度) 【更新于:2018-05-13】

这个题目最早在13年阿里笔试出现,直到前两天面试另一家电商又出现,哎,欠的都是要还的。

这个问题的思路如下:一维数组的下标连续的元素构成连续子数组,求所有连续子数组中和最大的那个子数组。

解析:2018-11-08

1 首先这个问题要转化为Q(n)的问题,对于Q(n)的问题要转化成Q(n) = Q(n-1) + An的问题。

2 只要能意识到这一点,其实就可以有希望在O(n)的时间内解决,不然,那就复杂的无法解决(因为你无法通过普通的循环遍历来做到,就算做到了,那复杂性也是难以承受的)。

3 上面的思想本质上还是一维动态规划算法。按照地归解分类讨论一下即可。

思路一旦确定,解的时候只需要更新各个临时变量和下标即可

解释(如上图所示):

1 Q(n)表示元素为n的数组其最优解(其下标范围为[i, j))

2 元素n+1为数组的第n+1个元素(其下标范围为n1)

3 A2为Q(n)产生时的一个子序列,这个序列的是Q(n)右侧和n+1之间的所有元素构成的集合,该序列的和一定是负值(下标范围为[j, n1))

4 A1为Q(n)产生时的Q(n)左侧的序列,该序列的和一定是负值

5 A1无需考虑,我们主要考虑加入第n+1元素时,新的Q(n+1)可能是那种情况即可。这里可能情况有四种。属于列举法解决问题。

6 首先考虑新的序列Q(n+1)会不会变的更长,也就是A2+(n+1)会不会也加入进来,这个是我们的直觉。这个时候会不会加进来主要看这个序列的和是不是大于零。也就是A2 + (n+1) > 0 ?  大于零也不是就一定要加上Q(n)。因为Q(n)可能是个负数,加了只会更小,这时候就不能加入,而A2又是和为负数,所以新的Q(n+1)只能是第n+1这个元素自己。这里的讨论只是以其中的一种情况为例讨论的。其余三种情况类似,参考上图即可。

7 本题的难点就在于列举出各种情况,很容易考虑不周,我就见过有人写的代码过于简单,考虑不周的。而且也写出来,这个是误人子弟(当然读者自己要去验证和辨别)

8 本题的第二个难点在于标记变量的设置和在各种情况下的更新。如果你把上图先画出来,对着图写程序,还是很简单的(即便这样,我的A2更新还是出过问题,改了两次才改好)。一定要严格定义上图中各个集合的范围,这时候他们在各种情况下的更新时机才会比较清晰,代码才不会写BUG。

代码:

#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

int MaxSumOfContinuousSubSequence(const vector<int>& a, int& i, int& j)
{
	if (a.size() == 0)
	{
		throw "sequence must has one element at least!";
	}

	if (a.size() == 1)
	{
		return a[0];
	}
	//各个集合及其下标范围
	//Q(n)[i,j) 上一次的最优解与下标范围
	//A2[j,n1)  上一次的最优解与当前第n+1个元素之间的序列(A2各元素和总是负值)
	//n1[n+1,n+2)  当前Q(n+1)比Q(n)多出来的那个新元素
	i = 0;//Q(n)的起始下标
	j = 1;//A2的起始下标
	int n1 = 1;//n1的起始下标

	int currentMaxSum = a[0];
	int sumA2 = 0;

	for (n1 = 1; n1 < a.size(); ++n1)
	{
		if (sumA2 + a[n1] >= 0)
		{
			if (currentMaxSum > 0)
			{
				currentMaxSum = currentMaxSum + sumA2 + a[n1];
				sumA2 = 0;
				j = n1 + 1;
			}
			else
			{
				currentMaxSum = a[n1];
				sumA2 = 0;
				i = n1;
				j = n1 + 1;
			}
		} 
		else
		{
			if (currentMaxSum > a[n1])
			{
				currentMaxSum = currentMaxSum;
				sumA2 = sumA2 + a[n1];
			}
			else
			{
				currentMaxSum = a[n1];
				sumA2 = 0;
				i = n1;
				j = n1 + 1;
			}
		}
	}

	return currentMaxSum;
}

void test_max_sub_sum(const vector<int>& a)
{
	cout << "sequence ";
	for each (auto var in a)
	{
		cout << var << " ";
	}
	cout << endl;
	int begin, end;
	cout << "MaxSumOfContinuousSubSequence " << MaxSumOfContinuousSubSequence(a, begin, end) << endl;
	for (int i = begin; i < end; ++i)
	{
		cout << a[i] << " ";
	}
	cout << endl<<endl;
}

int main()
{
	vector<int>  a1 = { -1, 100, -200, 13};
	test_max_sub_sum(a1);

	vector<int>  a2 = { -1, 100, -200, 201};
	test_max_sub_sum(a2);

	vector<int>  a3 = { -1, 100, -200, 201, -20, -20, -20, 61};
	test_max_sub_sum(a3);

	vector<int>  a4 = { -1, 100, -200, 201, -20, -20, -20, 61, -104, 103};
	test_max_sub_sum(a4);

	vector<int>  a5 = { -1, 100, -200, 201, -20, -20, -20, 61, -104, 103, 1};
	test_max_sub_sum(a5);

	return 0;
}

输出:

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C++程序员Carea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值