最大子数组问题的几种解法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最大子数组问题的几种解法相关的知识,希望对你有一定的参考价值。

参考技术A 最近看到《算法导论》的分治策略一节,看到的一个题目可以优化引申出来多种解法,同时也可以帮助理解分治策略的化整为零和动态规划的动态转移方程的思维。

最大子数组:数组 A 中的和最大的非空连续子数组。

这个问题可以用暴力解法,两层循环遍历,时间复杂度为 O(n^2),当然最容易想到的并不是最好的解法。

可以得知最大的和为 43,即下标 7, 10 之间的子数组。

既然这一节是讲分治策略,那么怎么用分治的思想来优化呢。这个解法确实比较难懂,如果让脑袋去跑一遍递归,真的有点累。那么分治本来就是一种局部整体的思想,我们把切片分成三组,左,中,右。那么我们只需要得出,这三个子集的最大值即可。然后再不断分化下去,最后把最大值冒上来。分治解法的关键就是如何用整体局部的思想把问题抽象化。

可以将时间复杂度降低到 O(n) 吗? 动态规划。

从题目上看,可以发现这道题满足动态规划的思想。可以求得动态转移方程为:F(n) = max(F(n-1)+A[n], A[n])

53 最大子序和

最大子数组问题全解

问题描述

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

问题解析

很经典的一个问题,下面给出3种解法,暴力解法、分治算法、动态规划。这个题Leetcode上有大量测试数据,只不过最后两个测试数据要求算法复杂度为n,只能用动态规划来解,可以借鉴一下,链接见这里https://leetcode.com/problems/maximum-subarray/description/

问题解决

1、暴力解法

穷举所有的子串,计算他们的和,然后从中找出最大的一个。

//最大子数组的暴力解法
int maxSubArray1(vector<int>& nums) {
    int max = INT_MIN;
    for (int i = 0; i < nums.size(); ++i) {
        for (int j = i; j < nums.size(); ++j) {
            int temp = 0;
            for (int k = i; k < j; ++k) {
                temp += nums[k];
            }
            if (temp > max)
                max = temp;
        }
    }
    return max;
}

2、分治算法

首先,将数组从中间分为两个部分

在此基础上我们考虑最大子串的情况,最大最子串的位置可能有两种:

第一种,只在Part1或是Part2之中,不跨越中间线。这种情况直接采用递归求出

第二种,跨越中间线。这种情况要寻找最大子串,我们从中间线开始,向左右两侧拓展,分别找到左侧最大值和右侧最大值,然后相加。

例如,所给数组为{ -2,1,-3,4,-1,2,1,-5,4 },中间位置为-1处,要求跨越中间线的最大子串,我们从-1开始向左遍历,得到子串{4,-1}、{-3,4,-1}、{1,-3,4,-1}和{-2,1,-3,4,-1}找出左侧最大子串{4,-1}。然后向右遍历,依次得到{-1,2}、{-1,2,1}、{-1,2,1,-5}、{-1,2,1,-5,4},其中右侧最大子串为{-1,2,1},最后将左侧最大子串和右侧最大子串相加即可。

//最大子数组的分治解法
int maxstr(vector<int>& nums,int left,int right) {
    //递归停止条件
    if (left == right)
        return nums[left];
    int mid = (left + right) / 2;
    //左侧递归
    int leftmax = maxstr(nums,left,mid);
    //右侧递归
    int rightmax = maxstr(nums,mid + 1, right);
    //从中间线向左寻找
    int lmax INT_MIN;
    int l = 0;
    for (int i = mid; i >= 0; --i) {
        l += nums[i];
        if (l > lmax)
            lmax = l;
    }
    //从中间线向右寻找
    int rmax = INT_MIN;
    int r = 0;
    for (int i = mid; i <= right; ++i) {
        r += nums[i];
        if (r > rmax)
            rmax = r;
    }
    int midmax = lmax + rmax -nums[mid];
    return  leftmax > rightmax?max(leftmax,midmax):max(rightmax,midmax);
}
int maxSubArray2(vector<int>& nums) {
    return maxstr(nums,0,nums.size()-1);
}

3、动态规划

这也是效率最高的一种办法,只需要遍历一遍,但思路有些难理解。

假设一个串a>0,那么这个串加上另一个串b一定比b要大,按照这个思路,从第一个数开始累加,如果累加之和小于0,则弃掉之前的数,从数组之后的位置重新开始累加,知道数组遍历完成,找出其中最大的数。(由于被弃掉时一定是遇到了一个绝对值大于前面所有正数的负数串,所以最大子串一定不能包含该负数串,因为在加上该负数串之前的串一定更大)

//最大子数组的动态规划解法
int maxSubArray3(vector<int>& nums) {
    int max = INT_MIN;
    int sum = 0;
    for (int i = 0; i < nums.size(); ++i) {
        sum += nums[i];
        if (sum > max)
            max = sum;
        if (sum < 0)
            sum = 0;
    }
    return max;
}

 

以上是关于最大子数组问题的几种解法的主要内容,如果未能解决你的问题,请参考以下文章

牛客网:连续子数组的最大和(动态递归解法)

最大子数组问题全解

连续子数组的最大和问题

贪心算法:最大子序和

最大子数组和

最佳解法剑指 Offer 42. 连续子数组的最大和