最大子串算法

最大子串问题是一类经典问题,即在一串整形数组中选取和最大的子串

给出问题描述:

对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。

针对本问题,可有三种方法,一种是暴利破解枚举算法,所有子串种类共有n+(n-1)+(n-2)+.....+1=n(n+1)/2个算法复杂度为o(n^2),然后找出最大的



这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为n的数组,其含有2^n个子序列和n(n+1)/2个子串。如果使用穷举法,则至少需要O(n^2)的时间才能得到答案。卡耐基梅隆大学的Jay Kadane的给出了一个线性时间算法,我们就来看看,如何在线性时间内解决最大子串和问题。


要说明Kadane算法的正确性,需要两个结论。首先,对于array[1...n],如果array[i...j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i...k]的和大于0。因为如果存在k使得array[i...k]的和小于0,那么我们就有array[k+1...j]的和大于array[i...j],这与我们假设的array[i...j]就是array中和最大子串矛盾。


其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p...q],是array的和最大子串,且array[p...q],跨越了array[i...j],array[j+1...k]。根据我们的分组方式,存在i<=m<j使得array[i...m]的和是array[i...j]中的最大值,存在j+1<=n<k使得array[j+1...n]的和是array[j+1...k]的最大值。由于array[m+1...j]使得array[i...j]的和小于0。此时我们可以比较array[i...m]和array[j+1...n],如果array[i...m]的和大于array[j+1...n]则array[i...m]>array[p...q],否array[j+1...n]>array[p...q],无论谁大,我们都可以找到比array[p...q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p...q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。对于单元素和最大的特例,该结论也适用。


根据上述结论,我们就得到了Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。



相关题目

PAT:1007Maximum Subsequence Sum

源码:(使用Kadane算法实现版本,算法时间复杂度为线性)

#include <iostream>
using namespace std;

int arrs[10001];

int Kadane(const int array[], size_t length,  int& left,  int& right)
{
	int i, cur_left, cur_right;
	int cur_max, max;
	
	cur_max = max = left = right = cur_left = cur_right = 0;
	max=-1;
	for(i = 0; i < length; ++i)
	{
		cur_max += array[i];

		if(cur_max > 0)
		{
			cur_right = i;

			if(max < cur_max)
			{
				max = cur_max;
				left = cur_left;
				right = cur_right;
			}
		}
		else
		{
			cur_max = 0;
			cur_left = cur_right = i + 1;
		}
	}

	return max;
}
int main(void)
{
	bool negative=false;
	int n;
	int left,right,maxs=-1;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>arrs[i];
		maxs=max(maxs,arrs[i]);
	}
	if(maxs==0)
	{
		/*此部分存在的问题是全都是负数,存在一个为0*/
		cout<<0<<" "<<0<<" "<<0;
		return 0;
	}
	
	int sums=Kadane(arrs,n,left,right);
	if(sums>=0)
		cout<<sums<<" "<<arrs[left]<<" "<<arrs[right];
	else
		cout<<0<<" "<<arrs[0]<<" "<<arrs[n-1];
	return 0;
}

源码版本2:(使用递归方法实现,算法复杂度为Nlog(N))

#include <iostream>

using namespace std;

int find_max_cross(int A[],int & low, int mid, int & high)
{
	int left_sum=-1<<20,sum=0;
	int max_left,max_right;
	
	for(int i=mid;i>=low;i--)
	{
		sum+=A[i];
		if(sum>left_sum)
		{
			left_sum=sum;
			max_left=i;
		}
	}
	int right_sum=-1<<20;
	sum=0;
	for(int i=mid+1;i<=high;i++)
	{
		sum+=A[i];
		if(sum>right_sum)
		{
			right_sum=sum;
			max_right=i;
		}
	}
	
	low=max_left;
	high=max_right;
	return right_sum+left_sum;
	
}

int find_max_sub(int A[],int& low,int & high)
{
	if(high==low)
		return A[low];
	else
	{
		int mid=(low+high)/2;
		int left_low,left_high,left_sum;
		int right_low,right_high,right_sum;
		int cross_low,cross_high,cross_sum;
		
		
		left_low=low;
		left_high=mid;
		left_sum=find_max_sub(A,left_low,left_high);
		
		right_low=mid+1;
		right_high=high;
		right_sum=find_max_sub(A,right_low,right_high);
		
		cross_low=low;
		cross_high=high;
		cross_sum=find_max_cross(A,cross_low,mid,cross_high);
		
		if(left_sum>=right_sum && left_sum>=cross_sum)
		{
			low=left_low;
			high=left_high;
			return left_sum;
		}
		else if(right_sum>=left_sum && right_sum>=cross_sum)
		{
			low=right_low;
			high=right_high;
			return right_sum;
		}
		else 
		{
			low=cross_low;
			high=cross_high;
			return cross_sum;
		}
	}
}


int main(void)
{
	int low,high;
	int maxs=0;
	int n;
	int A[10000]={-10,-1 ,-2 ,-3 ,-4 ,-5 ,-23, -3 ,-7, -21};
	
	cin>>n;
	low=0,high=n-1;
	for(int i=0;i<n;i++)
		cin>>A[i];
	maxs=find_max_sub(A,low,high);
	if(maxs<0)
		cout<<"0 "<<A[0]<<" "<<A[n-1];
	else
		cout<<maxs<<" "<<A[low]<<" "<<A[high];
	return 0;
}

实验测试结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值