最大子数组问题的三种算法

最大子数组问题描述如下:
一个数组有N个元素,求连续子数组的最大和,并返回其在原数组中的索引位置。例如,[-1,2,1]的最大连续子数组为[2,1],其和为3,索引位置为1和2(数组下标从0开始)。

下面以数组[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7]为例,进行各种算法的说明。

暴力解法

对具有n个元素的数组进行两次遍历,求取任意两个位置之间的数组的和,并记录,取最大值和最大值所在的索引为结果。时间复杂度为 O ( n 2 ) O(n^2) O(n2)

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;

int find_max_subarray(vector<int> v, int& max_left, int& max_right)
{
	int sum = -12345;
	int cursum = 0;
	for (int i = 0; i < v.size(); i++)
	{
		cursum = 0;
		for (int j = i; j < v.size(); j++)
		{
			cursum += v[j];
			if (cursum > sum)
			{
				sum = cursum;
				max_left = i;
				max_right = j;
			}
		}
	}
	return sum;

}

int main()
{
	int a[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	vector<int> v(a, a + 16);
	int low = 0, high = 15, max_left = 0, max_right = 0;
	int sum = find_max_subarray(v, max_left, max_right);
	cout << sum <<max_left<<max_right<< endl;
}

分治法
现在将这个大问题分为3个小问题,分的过程如下:首先找到数组的中间位置,mid,然后考虑求解两个子数组A[low,mid]和A[mid+1,high]。那么A[low,high]的任何连续子数组A[i,j]所处的位置必然就是下列三种情况之一:

  • 完全位于子数组A[low,mid]中,因此 l o w ≤ i ≤ j ≤ h i g h low \le i \le j \le high lowijhigh
  • 完全位于子数组A[mid+1,high]中,因此 m i d < i ≤ j ≤ h i g h mid < i \le j \le high mid<ijhigh
  • 跨越了中点,因此 l o w ≤ i ≤ m i d < j ≤ h i g h low \le i \le mid < j \le high lowimid<jhigh

我们可以递归的求解第一种和第二情况,因为这两个子问题依旧是最大子数组问题,只是规模变小了。因此,要求解的实际上就是第三种情况。

那么,给出代码如下:

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;

//求跨越中点的子数组最大值
int find_max_crossing_subarray(vector<int> v, int& low, int& mid, int& high,int& max_left,int& max_right)
{
	int left_sum = -12535;
	int sum = 0;
	for (int i = mid; i >= low; i--)
	{
		sum += v[i];
		if (sum > left_sum)
		{
			left_sum = sum;
			max_left = i;
		}
	}
	int right_sum = -12535;
	sum = 0;
	for (int i = mid+1; i <=high; i++)
	{
		sum += v[i];
		if (sum > right_sum)
		{
			right_sum = sum;
			max_right = i;
		}
	}
	return left_sum + right_sum;
}

//递归式
int find_maximum_subarray(vector<int> v, int& low, int& high, int& max_left, int& max_right)
{
	if (high == low)
		return v[low];
	else
	{
		int mid = (high + low) / 2;
		int mid_ = mid + 1;
		int left_sum = find_maximum_subarray(v, low, mid, max_left, max_right);
		int right_sum = find_maximum_subarray(v,mid_, high, max_left, max_right);
		int crossing_sum = find_max_crossing_subarray(v, low, mid, high, max_left, max_right);
		if ((left_sum >= right_sum) && (left_sum >= crossing_sum))
			return left_sum;
		else if ((right_sum >= left_sum) && (right_sum >= crossing_sum))
			return right_sum;
		else
			return crossing_sum;
	}
}

int main()
{
	int a[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	vector<int> v(a, a + 16);
	int low = 0, high = 15, max_left = 0, max_right = 0;
	int sum = find_maximum_subarray(v, low, high, max_left, max_right);
	cout << sum <<max_left<<max_right<< endl;
}

上述分治法的时间复杂度为 n log ⁡ ( n ) n\log (n) nlog(n)

动态规划

若已知前n个数的最大连续子数组,它的和记作dp[n]。那么考虑第n+1个数,此时,A[n+1]只有两种状态,
1.d[n]+A[n+1]<A[n+1],则说明d[n]<0,其最大和就是A[i]本身,此时最大连续子数组只有A[n+1]这一个数;
2.d[n]+A[n+1]>=A[n+1],则说明A[n+1]这个元素也是最大子数组中的一员,则把A[n+1]加入到前面,此时的最大连续子数组从前面某个数A[i]开始,到当前数A[n+1]结束。

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;

//动态规划
int find_max_subarray(vector<int> v, int& max_left, int& max_right)
{
	int sum = -12345;
	int a = 0;
	int tmp_max_left = 0;
	for (int i = 0; i < v.size(); i++)
	{
		if (a + v[i] < v[i])
		{
			a = v[i];
			tmp_max_left = i;
		}
		else
		{
			a = a + v[i];
		}
		if (a > sum)
		{
			sum = a;
			max_right = i;
			max_left = tmp_max_left;
		}
	}

	return sum;
}

int main()
{
	int a[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	vector<int> v(a, a + 16);
	int low = 0, high = 15, max_left = 0, max_right = 0;
	int sum = find_max_subarray(v, max_left, max_right);
	cout << sum <<max_left<<max_right<< endl;
}

上述算法的时间复杂度为 O ( n ) O(n) O(n)

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值