最大子序列和问题

问题描述:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><span style="font-size:18px">给定一个整数序列,a0, a1, a2, …… , an(项可以为负数),求其中最大的子序列和。如果所有整数都是负数,那么最大子序列和为0;</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>例如:对于序列-2, 11, -4, 13, -5, –2。 所求的最大子序列和为20(从11到13,即从a1到a3)。</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr>用于测试下面代码的的主函数代码如下:(注意要更改调用的函数名)</wbr></wbr></wbr></wbr></wbr></wbr>

int main(int argc, char **argv)
{
	 vector<int> a;
	a.push_back(-2);
	a.push_back(11);
	a.push_back(-4);
	a.push_back(13);
	a.push_back(-5);
	a.push_back(-2);
	int result;
	result = maxSubSum1(a); //在这里更改调用的函数名 cout<<result<<endl; //正确结果为 20 return 0;
}


方法一:对所有的子序列求和,在其中找到最大的

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><span style="font-size:18px">这是最容易想到的,也是最直接的,就是对<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">所有的子序列求和</span>,代码如下:</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

int maxSubSum1( const vector<int> &a )
{ 
	int maxSum = 0;
	for(int i=0; i<a.size(); i++ )
	{ 
		for(int j=i; j<a.size(); j++ )
		{ 
			int thisSum =0;
			for( int k=i; k<=j; k++ )
			{ 
				thisSum += a[k]; 
			}
			if(thisSum>maxSum)
			{ 
				maxSum = thisSum;
			}
		}
	}
	return maxSum;
}


分析:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>① 三层循环嵌套,时间复杂度为<span style="font-family:Arial"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; color:rgb(255,0,0); border-left-width:0px; padding-top:0px">O(n^3)</span></span>;</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>② 包含有大量的重复计算,例如i=1时: 当 j=3,则计算a1+a2+a3;j=4,则计算a1+a2+a3+a4;其中a1+a2+a3是重复计算的。</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

另一种思路:上面的方法在每一次循环中,固定i,并把a[i]当做起点,下面的方法将a[i]当做终点。

int maxSubSum1_2( const vector<int> &a )
{ 
	int maxSum = 0; 
	for(int i=0; i<a.size(); i++ )
	{ 
		for(int j=0; j<i; j++ )
		{ 
			int thisSum =0; 
			for(int k=j; k<=i; k++)
			{ 
				thisSum +=a[k]; //从节点j开始 累加到节点i
				if(thisSum>maxSum)
				{ 
					maxSum = thisSum; 
				}
			}
		}
	}
	return maxSum; 
}


  <wbr></wbr>

方法二:从某点开始的所有序列中,找最大的

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><span style="font-size:18px">如果你意识到,子序列总要有一个位置开始,那么变换一下循环方式,只要求出在<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">所有位置开始的子序列</span>,找到最大的。代码如下:</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

int maxSubSum2( const vector<int> &a )
{ 
	int maxSum = 0;
	for(int i=0; i<a.size(); i++ )
	{ 
		int thisSum =0;
		for(int j=i; j<a.size(); j++ )
		{ 
			thisSum +=a[j]; //从节点i开始 累加到结尾
			if(thisSum>maxSum)
			{ 
				maxSum = thisSum; 
			}
		}
	}
	return maxSum;
}


分析:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>① 两层循环嵌套,时间复杂度为<span style="font-family:Arial"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; color:rgb(255,0,0); border-left-width:0px; padding-top:0px">O(n^2)</span></span>;</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>② 虽然比方法一要少,但同样包含重复计算。例如:当i=1时,要计算a1+a2+a3+a4+……;当i=2时,要计算a2+a3+a4+……;其中a2+a3+a4+……是重复的。</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

注意:下面是一个错误的方法,因为它的起始点固定了,每次都从a0开始,是不能保证遍历所有的子序列的。

int maxSubSum2_2( const vector<int> &a )
{ 
	int maxSum = 0; 
	for(int i=0; i<a.size(); i++ )
	{ 
		int thisSum =0;
		for(int j=0; j<=i; j++ )
		{ 
			thisSum +=a[j]; //从节点0开始 累加到节点i
			if(thisSum>maxSum)
			{ 
				maxSum = thisSum; 
			}
		}
	}
	return maxSum;
}


如果希望将固定终点,那么计算的时候就要从终点开始,依次往前累加。代码如下:

int maxSubSum2_3( const vector<int> &a )
{ 
	int maxSum = 0; 
	for(int i=0; i<a.size(); i++ )
	{ 
		int thisSum =0;
		for(int j=i; j>=0; j-- )
		{//从节点i开始,向前累加到结尾0
			thisSum +=a[j];
			if(thisSum>maxSum)
			{ 
				maxSum = thisSum; 
			}
		}
	}
	return maxSum;
}


方法三:从某一个正数开始

第一步:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><span style="font-size:18px">到目前为止,题目的信息你只用到了:“最小子序列之和为0”(若有一项大于0,那么子序列的和一定大于或等于该项,也就大于0;因为若所有项都是负数,那么结果为0</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>如果你再挖掘一下题意:你就会发现,如果a[i]是负的,那么a[i]一定<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">不是</span>最终所有结果子序列的<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">起始点</span>。代码可以改造为:</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

int maxSubSum3_1( const vector<int> &a )
{ 
	int maxSum = 0;
	for(int i=0; i<a.size(); i++ )
	{ //(相对方法2,新增)如果a[i]<=0,那么a[i]一定不是所要求的起点,所以直接跳过去(利用for循环中有i++)
		if( a[i]<=0 )
	  	  continue;
		int thisSum =0;
		for(int j=i; j<a.size(); j++ )
		{ 
			thisSum +=a[j];
			if(thisSum>maxSum)
			{ 
				maxSum = thisSum; 
			}
		}
	}
	return maxSum;
}


第二步:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><span style="font-size:18px">如果你又再一步发现:<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">任何负的子序列,不可能作为最优子序列的前缀</span>。</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>又因为上一步已经保证,序列以正数开头a[i]&gt;0,所以若a[i]到a[j]之间元素的序列和 thisSum&lt;=0时,则i+1和j之间元素不会为最优子序列的前缀,可以让i=j,即不需要判断在i和j之间元素开头。代码如下</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

int maxSubSum3_2( const vector<int> &a )
{ 
	int maxSum = 0;
	for(int i=0; i<a.size(); i++ )
	{ //(相对方法2,新增)如果a[i]<=0,那么a[i]一定不是所要求的起点,所以直接跳过去(利用for循环中有i++)
		if( a[i]<=0 )
		  continue;
		int thisSum =0;
		for(int j=i; j<a.size(); j++ )
		{ 
			thisSum +=a[j];
			if(thisSum>maxSum)
			{ 
				maxSum = thisSum; 
			}
			else if( thisSum <= 0 )
			{ //(相对方法3_1 新添) thisSum = 0; i = j; }
		}
	}
	return maxSum;
}


<wbr></wbr>

第三步:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr><span style="font-size:18px">如果你又进一步发现:因为要求序列开始元素大于0,<span style="font-family:Arial"> 若以a[i]开头的序列,a[i]&gt;0,那么可以知道,所求的最终子序列一定不会以a[i+1]开始, 因为若到相同的元素终止,那么从a[i]开始序列,一定大于从a[i+1]开始的序列。因为s[i, k]=s[i+1, k]+a[i] 例如:a1+a2+a3&gt;0,而又由于这时a1&gt;0, 那么所求子序列一定不会以a2开始,因为从a1开始会更大。 更进一步,如若一个子序列thisSum&gt;0(其中thisSum是从第m项到第n项的和),那么序列一定不会以a[m]和a[n]之间的项开始。 因为一直thisSum第一个元素a[m]是大于0的,且以a[m]开始的所有子序列都是大于0的,因为若存在子序列小于0,就会提前返回了。 例如:若程序执行到thisSum (为a2+a3+a4),若thisSum&gt;0,则能说明,a2&gt;0,并且a2+a3&gt;0, a2+a3+a4&gt;0。那么 也可以跳过a[m]和a[n]之间的项,即另 i=j。</span></span></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

int maxSubSum3_3( const vector<int> &a )
{ 
	int maxSum = 0;
	for(int i=0; i<a.size(); i++ )
	{ //(相对方法2,新增)如果a[i]<=0,那么a[i]一定不是所要求的起点,所以直接跳过去(利用for循环中有i++)
		if( a[i]<=0 )
		  continue;
		int thisSum =0;
		for(int j=i; j<a.size(); j++ )
		{ 
			thisSum +=a[j];
			if( thisSum>0 )
			{ //(相对方法3_2 新添)
				if(thisSum>maxSum)
				{ maxSum = thisSum; }
				i = j; //(相对方法3_2 新添)
			}
			else if( thisSum <= 0 )
			{ //(相对方法3_1 新添) 
				thisSum = 0; 
				i = j;
			}
		}
	}
	return maxSum;
}


第四步:

最后如果你又了解一些程序结构上的优化的知识,那么你会发现下面的问题:

① 循环的分支可以改变一下,去除嵌套分支结构。

② 判断语句的分支中有共同部分,( i=j ),可以抽取出来。

以上两步以后,循环部分的代码编程 变成:

for(int i=0; i<a.size(); i++ ) 
{ 
	  if( a[i]<=0 ) 
	    continue; 
   	  int thisSum =0; 
	  for(int j=i; j<a.size(); j++ ) 
	  { 
		thisSum +=a[j]; 
		if(thisSum>maxSum) 
			{ maxSum = thisSum; } 
		else if( thisSum>0 ) 
			{ //do nothing } 
		else if( thisSum <= 0 ) 
			{ thisSum = 0; } 
	      
                i = j; 
	  } 
}


<wbr></wbr>

③ 下面这步非常重要,如果你发现,内层循环的循环变量j 和 外层循环的循环变量i<wbr><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">同步增长</span>,那么你是否能够想到,<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">外层循环可能没有存在的必要</span>。在这里到底能不能去除外层循环,取决于<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">外层循环中是否有额外的工作要做</span>。这里的额外工作是是if(a[i] &lt;=0) 判断语句,如果你能发现内层循环的 if(thisSum &lt; 0)的判断能够替代 if( a[i]&lt;=0 ) 的工作。因为thisSum是由a[j]得到的。</wbr>

到这里,代码就可以神奇的变为如下的形式:常量空间,线性时间

int maxSubSum3_4( const vector<int> &a ) 
{ 
	int maxSum = 0; 
	int thisSum = 0; 
	for(int j=0; j<a.size(); j++ ) 
	{ 
		thisSum += a[j]; 
		if(thisSum>maxSum) 
			{ maxSum = thisSum; } 
		else if( thisSum>0 ) 
			{ //do nothing } 
		else if( thisSum < 0 ) 
		{ thisSum = 0; } 
	} 
	return maxSum; 
}


分析:

<wbr><wbr><wbr><wbr><wbr><wbr><wbr>① 只有一层循环,时间复杂度为<span style="font-family:Arial"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; color:rgb(255,0,0); border-left-width:0px; padding-top:0px">O(n)</span></span>。<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">常量空间,线性时间,</span>这是<span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; color:rgb(255,0,0); border-left-width:0px; padding-top:0px"><span style="padding-bottom:0px; border-right-width:0px; margin:0px; padding-left:0px; padding-right:0px; border-top-width:0px; border-bottom-width:0px; border-left-width:0px; padding-top:0px">最优解法</span></span>。</span></span></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

你可能感兴趣的:(序列)