1003/最大连续子串和

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

这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为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。

下面是实现代码:

int Kadane(const int array[], size_t length, unsigned int& left, unsigned int& right)  
{  
    unsigned int i, cur_left, cur_right;  
    int cur_max, max;  
  
    cur_max = max = left = right = cur_left = cur_right = 0;  
  
    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;  
}  

这里我们需要注意,对于数组元素全为负的情况,由于不满足上述的两条结论,所以Kadane算法无法给出正确答案。

该问题是1977年Ulf Grenander提出的一个数字图像方面的问题,1984年Jay Kadane才给出了这个优美的解决方案。有些问题,看似解法简单,但是实际上其原理,要比代码复杂得多。

下面是我写的1003号题目代码,实现算法与上面大致一样,但是代码写的有点乱,不够简洁,精炼

# include <iostream>
# include <malloc.h>
# define CASES_MAX 20
using namespace std;

void FindMaxSum(int case_num,int nums_len,int * nums)
{
	int i,max_sum,t_sum;
	int left,right;
	int t_left,t_right;
	int first_flag = 1; //区别第一个非负数和后面的整数
	int t;
	for(i = 0;i < nums_len; ++i)
	{
		if(nums[i] >= 0 && first_flag == 1 )   //首先找到第一个非负数
		{
			left = i;
			right = i;
			t_left = i;
			t_right = i;
			max_sum = nums[i];
			t_sum = nums[i];
			first_flag = 0;
		}
		else if(first_flag==0)     //第一个非负数以后数字处理判断
		{
			t = t_right+1;  //区别开当下面连续字串断开时,已经进行了right t_sum加处理操作
			if(t <= i)
			{
			t_sum = t_sum + nums[i];
			t_right++;
			}
			if(t_sum > max_sum)   //临时连续子串和比已经存储的最大连续子串和大处理,赋值给最大连续字串和
			{
				max_sum = t_sum;
				left = t_left;
				right = t_right;
			}
			else if(t_sum < 0 && t_right+1<nums_len && nums[t_right+1]>=0) //临时字串和小于0,临时连续字串断开,从下一位非负数开始
			{
				t_right++;
				t_left = t_right;
				t_sum = nums[t_right]; 
			}
		
		}
		
	}
	if(first_flag == 1)  //没有找到非负数,也就是数组全部负数处理
	{
		max_sum = nums[0];
		left = right = 0;
		for(i = 0; i < nums_len; ++i)
			if(nums[i] > max_sum)
			{
				max_sum = nums[i];
				left = i;
				right = i;
			}
	}

		if(case_num != 0)
			cout<<endl;
		cout<<"Case "<<(case_num+1)<<":"<<endl;
		cout<<max_sum<<" ";
		cout<<(left+1)<<" ";
		cout<<(right+1)<<endl;
}
int main()
{
	int num_case;//输入的组数
	int *  nums_address[CASES_MAX];//存储每组数据的首地址,最大达到题目要求上限20   
	int i,j;
	

	cin>>num_case;
	int * case_lens = (int *)malloc(sizeof(int) * num_case);//int型数组case_lens存储每组数据的长度 
	
	for( i = 0;i < num_case; ++i)
	{
		cin>>(*(case_lens + i));
		nums_address[i] = (int *)malloc(sizeof(int) * ( *(case_lens + i) ) ) ;

		for( j = 0; j < (*(case_lens + i)); ++j)
			cin>>(*(nums_address[i] + j));
	}
	for(i = 0;i < num_case;++i)
		 FindMaxSum(i,(*(case_lens + i)), nums_address[i]);

	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值