LeetCode-53-最大子数组和-简单(贪心/分治/暴力求解/动态规划)

一 题目

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。

二 示例及提示

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

    1 <= nums.length <= 105
    -104 <= nums[i] <= 104

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

三 题解

1.贪心

需要遍历整个数组1次,时间复杂度O(n) 。使用了两个变量保存当前和与最大和,只使用了常数空间,空间复杂度O(1)。

附上当时写题的乱七八糟的思路:

核心思想:若当前遍历到的元素之前的和小于0,则直接舍弃掉前面的数列。

代码如下:

class Solution {
public:
    int maxSubArray(vector& nums) {
        int max = nums[0];
        int curSum = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            if(curSum >= 0)
            {
                curSum += nums[i];
            }else{
                curSum = nums[i];
            }
            if(curSum > max)
            {
                max = curSum;
            }
        }
        return max;
    }
};

注意:

1.max必须取nums[0],并不能取0什么的,因为nums里最大的数可能就是负数。

2.curSum>max的判定语句要放最后,因为nums[0]<0时,会直接将max更新成0了,违反了第一条

2.分治策略

分治策略就是将问题分解为多个子问题,将子问题再分解出子问题……且这些子问题相互独立并与原问题形式相同或相似,递归地解决这些子问题后,将各个子问题的解合并得到原问题的解。

像排序算法中涉及到的分治策略有:二分法、快排和希尔排序等。

思路:

分治策略的步骤通常为以下三步:

分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

解决:若子问题规模较小而容易被解决则直接解,否则【递归地】解各个子问题;

合并:将各个子问题的解合并为原问题的解。

而回到原题,对于区间(l,r),可以分解为左(l,m)和右(m+1,r)两个区间,再对这两个区间分别再不断拆分至规模足够小的子问题得到直接解……因此可以找到递归终止,也就是不可再拆分的条件为:当区间长度为1,left==right。同时,对于(l,r),最大子数组之和可能恰巧在左区间内,也可能在右区间内,甚至可以出现在左区间的后半段+右区间的前半段。因此,其出现情况又大致可以分成两种:

        1.跨越了中点m。→③以中点为起始点,向前后左右区间两部分分别往下延伸找

        2.不跨越m。  →找①左/②右区间的最大子数组和

最后,再比较这三种结果,取最大者。

代码如下:

class Solution {
public:
    int findSpan(vector& nums, int left, int right, int mid){
        int curSum = nums[mid];
        int leftSum = nums[mid], rightSum = nums[mid];
        for(int i = mid - 1; i >= left; i--)
        {
            curSum += nums[i];
            if(curSum > leftSum)
                leftSum = curSum;
        }
        curSum = nums[mid];
        for(int i = mid + 1; i <= right; i++)
        {
            curSum += nums[i];
            if(curSum > rightSum)
                rightSum = curSum;
        }

        return (leftSum + rightSum - nums[mid]);
    }

    int maxSum(vector& nums, int left, int right){
        if(left == right)
        {
            return nums[left];
        }else{
            int mid = (left + right) / 2;
            int left_max,right_max,mid_max;
            left_max = maxSum(nums,left,mid);
            right_max = maxSum(nums,mid+1,right);
            mid_max = findSpan(nums,left,right,mid);
            return max(mid_max,max(left_max,right_max));
        }
    }

    int maxSubArray(vector& nums) {
        return maxSum(nums,0,nums.size()-1);
    }
};

3.暴力求解

时间复杂度O(n^2)。就是超时了:D

class Solution {
public:
    int maxSubArray(vector& nums) {
        int max = nums[0];
        int sum;
        for(int i = 0; i < nums.size(); i++)
        {
            for(int j = i; j < nums.size(); j++)
            {
                sum += nums[j];
                max = sum > max ? sum : max;
            }
            sum = 0;
        }
        return max;
    }
};

4.动态规划

先来了解一下动态规划的概念,以下一段来自百科:

基本思想:动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。

通俗的说就是,你要做出一个宏大的决策,那么可以先试图将该决策拆分成多个阶段的决策问题,且每个阶段都会或多或少的影响下一个阶段的决策,因此可以保留前一个阶段的决策的答案,避免重复的计算。

最大子序和这道题就很适合举例,接触下来我觉得动态规划和贪心策略很相像。动态规划有两种特征:1)重复子问题2)最优子结构。而贪心策略是对于问题的求解,总是做出当前的最优解,而不从整体考虑,其特征也有两个:1)贪心选择策略2)最优子结构。

但要注意的是,贪心策略并不总能得到问题的整体最优解,因此对于一个问题能否用贪心算法,必须证明每一步所作的贪心选择会最终导致问题的整体最优解。

LeetCode-53-最大子数组和-简单(贪心/分治/暴力求解/动态规划)_第1张图片LeetCode-53-最大子数组和-简单(贪心/分治/暴力求解/动态规划)_第2张图片

思路:

dp[i]是指以nums[i]结尾的最大子序和。据说找最大最小值时,要将初始值定义为INT_MAX 或者INT_MIN。

class Solution {
public:
    int maxSubArray(vector& nums) {
        int maxSum = INT_MIN;
        vectordp(nums.size());
        dp[0] = nums[0];
        maxSum = nums[0];
        for(int i = 1; i < nums.size(); i++){
            dp[i] = max(dp[i-1]+nums[i],nums[i]);
            maxSum = max(dp[i],maxSum);
        }
        return maxSum;
    }
};

时间复杂度O(n) 空间复杂度O(n)。但是空间复杂度可以优化为O(1),因为只需要知道前一项dp[i-1]的大小,所以可以用整型数据代替数组。

你可能感兴趣的:(LeetCode,leetcode,算法,散列表)