LintCode 最大子数组(3种方法)

给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。

样例

给出数组[−2,2,−3,4,−1,2,1,−5,3],符合要求的子数组为[4,−1,2,1],其最大和为6

方法一:暴力枚举,时间复杂度O(n3)

1、找出子数组的最左端点     for i<-1 to n

2、找出子数组的最右端点     for j<-i  to n

3、求和,找出最大值            sum = a[i] +……+a[j];  ans = max(ans, sum)

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: A integer indicate the sum of max subarray
     */
    int maxSubArray(vector<int> nums) {
        // write your code here
        int n = nums.size();
        int ans = -1000000;      //因为求最大值,所以给结果初始化一个很小的数
        for(int i=0; i<n; i++)
        {
            for(int j=i; j<n; j++)
            {
                int sum = 0;
                for(int k=i; k<=j; k++)
                {
                    sum += nums[k];
                }
                if(sum > ans)
                {
                    ans = sum;
                }
            }
        }
        return ans;
    }
};

但是很明显时间复杂度太大,只是作为一种最基础的方法,在此基础上再考虑优化方法。

慢在哪里?找冗余操作,执行次数最多的操作。求和操作被执行了很多次,有很多是重复计算的。


方法二:优化枚举,时间复杂度O(n2)

左端点相同的子数组,随着长度的增加,排在前面的元素被不断重复相加。如果我们记录下之前的求和结果,

就不需要对前面的元素再进行计算,比方法一少了一重循环。

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: A integer indicate the sum of max subarray
     */
    int maxSubArray(vector<int> nums) {
        // write your code here
        int n = nums.size();
        int ans = -1000000;
        for(int i=0; i<n; i++)
        {
            int sum = 0;
            for(int j=i; j<n; j++)
            {
                sum += nums[j];
                if(sum > ans)
                {
                    ans = sum;
                }
            }
        }
        return ans;
    }
};

显然O(n2)的复杂度还是不令人满意。继续寻找冗余操作。

方法三:动态规划,复杂度O(n)

把子问题F(i)定义为:以num[i]结尾的连续子串的最大和,则有F(i) = max( num[i], F(i-1)+num[i] )。这取决于F(i-1)是正还是负,如果是整数,则F(i) = F(i-1)+num[i]; 如果是负数,则F(i) = num[i]。

LeetCode上一句评论是这么说的:这道题目的思想是: 走完这一生 如果我和你在一起会变得更好,那我们就在一起,否则我就丢下你。 我回顾我最光辉的时刻就是和不同人在一起,变得更好的最长连续时刻。

贪心法:如果子串A的和是负数,而子串B包含子串A,那B则不需要进行计算。这样就省去了一些计算步骤。我们可以在遍历过程中将子串和为负数的子串丢掉,只留和为正的子串。

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: A integer indicate the sum of max subarray
     */
    int maxSubArray(vector<int> nums) {
        // write your code here
        int n = nums.size();
        int ans = -1000000;

        //标准的动态规划写法
        vector<int> f(n);
        f[0] = nums[0];
        ans = f[0];
        for(int i=1; i<n; ++i){
            f[i] = f[i-1] > 0 ? f[i-1]+nums[i] : nums[i];

            ans = max(ans, f[i]);
        }
        return ans;


        //贪心法
        int sum = 0;
        for(int i=0; i<n; i++)
        {
            sum += nums[i];
            if(sum > ans)
            {
                ans = sum;
            }
            if(sum < 0)
            {
                sum = 0;   //子串和为负数,丢掉
            }
        }
        return ans;
    }
};

方法四:分治法,复杂度O(nlogn)

求数组[l, r]的最大子序列和,可以从中点拆分数组为两部分[l, m] 和 [m+1, r]。最大子序列肯定在以下3种情况中出现:

1、左半部分

2、右半部分

3、横跨左右两半部分

如果是情况3,那就分别求两半部分的最大和再加起来。不断拆分数组,直到数组只包含一个数时,最大值就是这个数。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();

        return findSub(nums, 0, n-1);
    }

    int max3(int a, int b, int c) {
        return max(a, max(b, c));
    }

    // 横跨mid的时候求最大和
    int findCrosss(vector<int>&nums, int left, int mid, int right) {
        int sum = 0;
        int leftSum = INT_MIN;
        int rightSum = INT_MIN;
        for(int i=mid; i>=left; --i) {
            sum += nums[i];
            if (sum > leftSum) leftSum = sum;
        }

        sum = 0;
        for(int i=mid+1; i<=right; ++i) {
            sum += nums[i];
            if(sum > rightSum) rightSum = sum;
        }

        return (leftSum+rightSum);

    }

    int findSub(vector<int>&nums, int left, int right) {
        if (left == right) return nums[left]; // 拆分成的最小数组

        int mid = left + (right - left)/2;
        return max3(findSub(nums, left, mid), findSub(nums, mid+1, right), findCrosss(nums, left, mid, right));
    }
};

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值