代码改变世界

数组中子数组之和最大问题

2013-08-19 00:34  夜与周公  阅读(348)  评论(0编辑  收藏  举报

  问题描述:给定一个包含N个元素的数据A(A[0], A[1], A[2]...A[N-1]),这个数组自然有很多子数组,那么,这些子数组之和的最大值是什么?

  Example: 数组[1, -2, 3, 5, -3, 2],最大的子数据是[3, 5],和为8。

  解法一:穷举法

  最直接,也是最简单的方法,穷举子数组A[i:j]的和,找出一个最大的,算法的时间复杂度O(N2),算法的代码如下:

#include <iostream>
#include <climits>

using namespace std;

int max_sub_sum(const int* A, int n)
{
    int max_sum = INT_MIN;

    for (int i = 0; i < n; i++)
    {
        int sub_sum = 0;
        for (int j = i; j < n; j++)
        {
            sub_sum += A[j];                //sum(i,j)
            if ( max_sum < sub_sum)
            {
                max_sum = sub_sum;
            }
        }
    }

    return max_sum;

}

int main()
{
    int A[] = {1, -2, 3, 5, -3, 2};
    int max_sum = max_sub_sum(A, sizeof(A) / sizeof(int));
    cout<<"max sub sum:"<<max_sum<<endl;
    system("pause");
    return 0;
}

  解法二:分治法

  将数组分成两个子数组:A[0:n/2]和A[n/2 + 1, n]那么最大的组数据之和,必然出现在以下三种情况;

  1)最大子段与A[0:n/2]重合;

  2)最大子段与A[n/2 + 1, n]重合

  3)最大子段夸过A[0:n/2]和A[n/2 + 1, n-1]两段。

  对于1)、2)两种情况可以递归解决,而对于第三种情况,我们只要找到A[0:n/2]以n/2为终点最大和,A[n/2 + 1, n-1]以n/2 + 1为起点的最大和,两种相加即可。

算法的时间复杂度为O(NlogN),代码如下:

#include <iostream>
#include <climits>

using namespace std;

int max_sub_sum(int* A, int i, int j)
{
    if (i == j)
    {
        return A[i];
    }

    int mid = (i + j) / 2;
    int max_sum = max(max_sub_sum(A, i, mid), max_sub_sum(A, mid+1, j));
    
    int left_mid_max_sum = INT_MIN;
    int mid_sum = 0;
    for (int k =mid; k >= i; k--)
    {
        mid_sum += A[k];
        if (left_mid_max_sum < mid_sum)
        {
            left_mid_max_sum = mid_sum;
        }
    }

    int right_mid_max_sum = INT_MIN;
    mid_sum = 0;
    for (int k = mid+1; k <= j; k++)
    {
        mid_sum += A[k];
        if (right_mid_max_sum < mid_sum)
        {
            right_mid_max_sum = mid_sum;
        }

    }

    max_sum = max(max_sum, left_mid_max_sum + right_mid_max_sum);

    return max_sum;

}

int main()
{
    int A[] = {1, -2, 3, 5, -3, 2};
    int max_sum = max_sub_sum(A, 0, (sizeof(A) / sizeof(int)) -1);
    cout<<"max sub sum:"<<max_sum<<endl;
    system("pause");
    return 0;
}

  解法三:动态规划

  动态规划问题依赖于两个基本因素:1)最优子结构;2)重叠结构。首先定义两个量All(i)和Start(i),All(i)表示从A[i:n-1]数组中最大的子段和,Start(i)则表示了以下表i作为起始位置的最大子段和,那么该问题的最优的递归表达式:

  All(i) = max(All(i+1), start(i))

  这个公式表示,All(i)取:从i下标开始的字段数组start(i),从i+1开始的最大字段和All(i+1)之间的最大值, start(i)的定义如下:

  start(i) = max(A[i], A[i] + start(i+1))

该解法的代码如下:

#include <iostream>
#include <climits>

using namespace std;

int max_sub_sum(int* A, int n)
{
    int all = A[n-1];
    int start = A[n -1];

    for (int i = n -2; i >= 0; i--)
    {
        start = max(A[i], A[i] + start);        //start(i) = max(A[i], start(i+1))
        all = max(all, start);
    }

    return all;
}

int main()
{
    int A[] = {1, -2, 3, 5, -3, 2};
    int max_sum = max_sub_sum(A, sizeof(A) / sizeof(int));
    cout<<"max sub sum:"<<max_sum<<endl;
    system("pause");
    return 0;
}

  最后一种方法是淳朴的迭代算法。

  迭代算法看起来简单,但是这个问题但有着极其惊人的疗效:1)用变量per_sum保存当前以i-1作为结束下标的字段数组和,如果per_sum小于0(前面的部分起到了“负面”作用,需要舍弃),从下标i开始从新字段数组,于此同时,不断比较每次字段数组的最大值,当迭代结束是,便会得到字段数组的组大和。 

#include <iostream>
#include <climits>

using namespace std;

int max_sub_sum(int* A, int n)
{
    int max_sum = INT_MIN;
    int sum = 0;

    for (int i = 0; i < n; i++)
    {
        if (sum < 0)                //前面的元素提到了负面作用,需要舍弃
        {
            sum = A[i];
        }
        else
        {
            sum += A[i];            //前面的元素提到了正面作用,需要保留
        }
        if (sum > max_sum)
        {
            max_sum = sum;
        }
    }

    return max_sum;

}
int main()
{
    int A[] = {1, -2, 3, 5, -3, 2};
    int max_sum = max_sub_sum(A, sizeof(A) / sizeof(int));
    cout<<"max sub sum:"<<max_sum<<endl;
    system("pause");
    return 0;
}