最大子数组问题 C++分治与动态规划求解

0.问题定义

关于最大子数组问题的定义不再赘述。


1.分治解法

代码如下:

#include <iostream>
using namespace std;

struct info {
	int sum, low, high;
	info(int sum_, int low_, int high_) : sum(sum_), low(low_), high(high_) {}
};

class maxSubarrayProblem {
private:
	int *arr;
	int size;
public:
	maxSubarrayProblem(int *arr_, int size_) {
		arr = new int[size = size_];
		for (int i=0; i<size; ++i)
			arr[i] = arr_[i];

		info res = findMaxSubArr(0, size-1);

		cout << "result is:" << res.low << " to " << res.high << " sum as " << res.sum;
	}

	info findMaxSubArr(int low, int high) {
		if (low==high) // base case
			return info(arr[low], low, high);

		int mid = (low+high)/2;

		info lh = findMaxSubArr(low, mid);
		info rh = findMaxSubArr(mid+1, high);
		info mh = findCroSubArr(low, mid, high);

		// choose biggest to return
		if (lh.sum>rh.sum && lh.sum>mh.sum)
			return lh;
		else if (rh.sum>lh.sum && rh.sum>mh.sum)
			return rh;
		else
			return mh;
	}

	info findCroSubArr(int low, int mid, int high) {
		int ls = 0, rs = 0, lp = 0, rp = 0;
		int lm = arr[mid]-1, rm = arr[mid+1]-1;
		// left side max value
		for (int i=mid; i>=low; --i) {
			ls += arr[i];
			if (ls>lm) {
				lp = i;
				lm = ls;
			}
		}
		// right side max value
		for (int i=mid+1; i<=high; ++i) {
			rs += arr[i];
			if (rs>rm) {
				rp = i;
				rm = rs;
			}
		}
		// get and return crossing middle max value.
		return info(lm+rm, lp, rp);
	}
};

int main() {
	// 7-10 43
	int a[]{13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5,
			-22, 15, -4, 7};

	maxSubarrayProblem test(a, 16);
}

算法分析:

  1. 分治递归策略。将数组依次二分以缩减问题规模,拿第一次二分来说,如果整个数组存在一个最大子数组,那么它要么在划分后的左半部分,要么在右半部分,要么跨过中线,正是依据这个思路我们将问题一直缩减,缩减到单个元素时为base case。在一次划分返回时,返回这三种情况中最大的值以及当前这一个最大子数组(不一定是全局最大的)所覆盖的范围(下标起始位置与终止位置),最终在最后一次返回(也就是第一次划分)时得到我们要的结果。
  2. 因为采用分治递归策略,算法时间复杂度为O(nlgn)

2.动态规划解法

代码如下:

#include <iostream>
using namespace std;

class dpFindMaxSubArr {
private:
	int *arr, *dp;
	int size;
	int minPos, maxPos, maxValue;
public:
	dpFindMaxSubArr(int arr_[], int size_) {
		arr = new int[size = size_];
		for (int i=0; i<size_; ++i)
			arr[i] = arr_[i];
	}

	void dp_() {
		dp = new int[size]();

		dp[0] = arr[0];
		for (int i=1; i<size; ++i)
			dp[i] = max(dp[i-1]+arr[i], arr[i]); // 这一步是关键,如何构建dp数组

		// get max value
		maxValue=dp[0]; maxPos=0;
		for (int i=1; i<size; ++i)
			maxValue=maxValue<dp[i]?(maxPos=i, dp[i]) : maxValue;

		// find begin position
		minPos = maxPos;
		for (int i=maxPos; i>=0; --i)
			if (dp[i]>0)
				minPos = i;
			else
				break;
	}

	void showAnswer() {
		cout << "from " << minPos << " to " << maxPos << " sum as " << maxValue << endl;
	}
};

int main() {
	// 7-10 43
	int a[]{13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
	// 0 to 3 sum as 10
	//int a[]{1, 2, 3, 4};

	dpFindMaxSubArr test(a, 16);
	test.dp_();
	test.showAnswer();
	return 0;
}

算法分析:

  1. 最重要的是得出动态规划算法的核心:递推表达式。
    这里表达式为,

d p [ i + i ] = m a x ( d p [ i ] + a [ i + 1 ] , a [ i + 1 ] ) ; dp[i+i] = max(dp[i]+a[i+1], a[i+1]); dp[i+i]=max(dp[i]+a[i+1],a[i+1]);

大致思路是如果当前元素加上前面传过来的最大值大于0,那么就加在一起(因为只要大于0那么和我加在一起我肯定是比原来的值要大的);如果加在一起之后还没有我本身大(也就是说传过来一个负数),那么就抛弃这个数,自己和自己一起

  1. 时间复杂度为线性O(n)。第一次扫一遍得到dp数组,第二次扫dp数组得到最大子数组的范围以及最大值。
  2. 难点在于如果定义dp数组及其计算表达式
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值