最大连续子数组和

A. 问题描述

给定一个整数数组,元素的值有正有负。定义“连续子数组和”为连续几个数组的元素的和,求最大的连续子数组和。已知这个值在int能够表示的范围内。

B. 暴力做

  1. 无脑暴力做就是枚举所有的子数组,O(n^2),然后对于每个子数组求和,自然就找出最大的了,复杂度总共是O(n^3)。
  2. 能否优化?想想哪里做了重复多余的事情了?
  3. 没错,就是对每个子数组求和的时候,先做一下前缀和+后缀和的预处理,就可以将子数组求和的复杂度将为O(1)了,总的复杂度为O(n^2)。

具体实现代码如下:

int MaxSubArraySum(const vector<int>& v) {
    int size = v.size();
    vector<int> forward(size+1), backward(size+1);

    // 计算前缀和
    for (int i = 0; i < size; ++i)
        forward[i+1] = forward[i] + v[i];

    // 计算后缀和
    for (int i = size-1; i >= 0; --i)
        backward[i] = backward[i+1] + v[i];

    // 获取整个数组所有元素的和
    int AllSum = 0;
    for (int i = 0; i < size; ++i)
        AllSum += v[i];

    int maxSum = AllSum;
    // 枚举所有子数组,并求连续最大子数组和
    for (int i = 0; i < size; ++i) {
        for (int j = i; j < size; ++j)
            maxSum = max(maxSum, AllSum - forward[i] - backward[j+1]);
    }

    return maxSum;
}

还有更快更好的方法吗?

C. 更好的方法

其实这是一道很简单的dp问题,有O(n)的做法的。
定义dp[i]为以第i位元素结尾的所有子数组中的和最大的那个和的值,那么转移方程很显然是:

dp[i] = max(dp[i-1] + X[i], X[i])

它的意思是,我自己这一位(即第i位)要和前面的凑到一起成为一组呢,还是我自己一个人一组呢?看谁大咯~写起来就几行而已:

int MaxSubArraySum2(const vector<int>& v) {
    int size = v.size(), maxSum = v[0];
    vector<int> dp(size);
    dp[0] = v[0];
    for (int i = 1; i < size; ++i) {
        dp[i] = max(dp[i-1] + v[i], v[i]);
        maxSum = max(maxSum, dp[i]); 
    }

    return maxSum;
}

D. 总结

笨方法不一定是容易的,就像B和C的对比一样,2333,多接触一些好玩的算法,既可以丰富知识,也可以灵活自己的头脑,何乐不为?
最后附上自己此次的测试代码:

#include <stdio.h>
#include <ctime>
#include <cstdlib>
#include <vector>
#include <assert.h>
using namespace std;

vector<int> gen(int size) {
    int MAX = 100000;
    vector<int> v(size);
    for (int i = 0; i < size; ++i) {
        int key = rand() % MAX; 
        v[i] = (key > MAX/2) ? (key) : (-key);
    }
    return v;
}

void display(const vector<int>& v) {
    for (int i = 0; i < v.size(); ++i)
        printf("%d ", v[i]);
    printf("\n");
}

// 暴力+前后缀和优化
int MaxSubArraySum(const vector<int>& v) {
    int size = v.size();
    vector<int> forward(size+1), backward(size+1);

    // 计算前缀和
    for (int i = 0; i < size; ++i)
        forward[i+1] = forward[i] + v[i];

    // 计算后缀和
    for (int i = size-1; i >= 0; --i)
        backward[i] = backward[i+1] + v[i];

    // 获取整个数组所有元素的和
    int AllSum = 0;
    for (int i = 0; i < size; ++i)
        AllSum += v[i];

    int maxSum = AllSum;
    // 枚举所有子数组,并求连续最大子数组和
    for (int i = 0; i < size; ++i) {
        for (int j = i; j < size; ++j)
            maxSum = max(maxSum, AllSum - forward[i] - backward[j+1]);
    }

    return maxSum;
}


// 动态规划求解
int MaxSubArraySum2(const vector<int>& v) {
    int size = v.size(), maxSum = v[0];
    vector<int> dp(size);
    dp[0] = v[0];
    for (int i = 1; i < size; ++i) {
        dp[i] = max(dp[i-1] + v[i], v[i]);
        maxSum = max(maxSum, dp[i]); 
    }

    return maxSum;
}

int main() {
    srand(time(0));

    for (int i = 0; i < 1000; ++i) {
        int size = rand() % 997;
        vector<int> v = gen(size);
        if (MaxSubArraySum(v) != MaxSubArraySum2(v)) {
            display(v);
            printf("Your algorithm didn't work at this test cast...\n");
            exit(0);
        }
        printf("Pass test case %d\n", i+1);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值