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);
}
算法分析:
- 分治递归策略。将数组依次二分以缩减问题规模,拿第一次二分来说,如果整个数组存在一个最大子数组,那么它要么在划分后的左半部分,要么在右半部分,要么跨过中线,正是依据这个思路我们将问题一直缩减,缩减到单个元素时为base case。在一次划分返回时,返回这三种情况中最大的值以及当前这一个最大子数组(不一定是全局最大的)所覆盖的范围(下标起始位置与终止位置),最终在最后一次返回(也就是第一次划分)时得到我们要的结果。
- 因为采用分治递归策略,算法时间复杂度为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;
}
算法分析:
- 最重要的是得出动态规划算法的核心:递推表达式。
这里表达式为,
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那么和我加在一起我肯定是比原来的值要大的);如果加在一起之后还没有我本身大(也就是说传过来一个负数),那么就抛弃这个数,自己和自己一起。
- 时间复杂度为线性O(n)。第一次扫一遍得到dp数组,第二次扫dp数组得到最大子数组的范围以及最大值。
- 难点在于如果定义dp数组及其计算表达式