Leetcode算法学习日志-53 Maximum Subarray

来源:互联网 发布:私募公募 知乎 编辑:程序博客网 时间:2024/06/05 05:58

Leetcode 53 Maximum Subarray

题目原文

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

For example, given the array [-2,1,-3,4,-1,2,1,-5,4], the contiguous subarray[4,-1,2,1] has the largest sum =6.

题意分析

题目要求找到数组中一个连续的子数组,它的和最大,输出这个最大的和。可以看出如果采用Brute Force,算法的复杂度应该为O(n^2)。

解法分析

本题我选择的编程语言是C++,本题解法主要有以下两个:

  • Divide and Conquer(分治法)
  • Dynamic Programming(动态规划)

Divede and Conquer

要在数组里找一个和最大的数组,自然想到把用分治法这个数组分成两个数组,分别找到其中和最大的子数组。本题将数组分为左右两个子数组后,原数组和最大子数组的位置有以下三种情况:

  • 为左子数组的和最大数组
  • 为右子数组的和最大数组
  • 在左右数组分界处的子数组

需要判断上面三个子数组哪一个的和最大,输出最大和。C++代码如下:

class Solution {public:    int maxSubArray(vector<int>& nums) {        int n=nums.size();        int res=maxSub(0,n-1,nums);        return res;            }private:    int maxSub(int left,int right,vector<int>& nums){        if(left==right)            return nums[left];        int mid=(left+right)/2;        int maxLeft=maxSub(left,mid,nums);        int maxRight=maxSub(mid+1,right,nums);        int lMaxNearMid=nums[mid];        int lTemp=nums[mid];        int rMaxNearMid=nums[mid+1];        int rTemp=nums[mid+1];        int i,j;        for(i=mid-1;i>=left;i--){            lTemp=lTemp+nums[i];            if(lTemp>lMaxNearMid)                lMaxNearMid=lTemp;        }        for(j=mid+2;j<=right;j++){            rTemp=rTemp+nums[j];            if(rTemp>rMaxNearMid)                rMaxNearMid=rTemp;        }        if((maxLeft>=maxRight)&&(maxLeft>=(lMaxNearMid+rMaxNearMid)))            return maxLeft;        else if(maxRight>=(lMaxNearMid+rMaxNearMid))            return maxRight;        else            return lMaxNearMid+rMaxNearMid;            }};
由于mid=n/2位置是确定的,所以由这一点开始向左、右求最大和算法复杂度为O(n),所以整个算法的递归式为T(n)=2T(n/2)+O(n),O(n)为’合并‘两子列时的时间消耗,所以总的算法复杂度为O(nlogn)。其中将原数组分成两个子数组可以利用迭代器来完成,可以用如下代码定义生成左子数组left:

vector<int> left(nums.begin(),nums.begin()+nums.size()/2);

上述代码相当于指定了vector left的begin()和end()。

Dynamic Programming

动态规划方法常常用于求解最优化问题,和分治法相同的是,动态规划方法也是将原问题分解为许多字问题,通过组合子问题的解来求解原问题。而它们之间的不同点在于,分治法往往将问题分解为互不相交的子问题,而动态规划方法用于子问题有重叠的情况,也就是有相同子子问题,这时候利用分治的思想会反复求解同一个子问题,也就是那些公共子问题,而动态规划方法对每个子问题只求解一次,将其解存在数组(下标有序)或散列表(下标无序)中,避免了不必要的计算工作,这也同时要求问题满足最有子结构,也即问题的最优解是由相关子问题的最优解组合而成的。这也是典型的空间换时间的时空权衡例子。

动态规划方法主要有两种实现形式:

  • 带备忘的自顶向下法

这种方法采用递归形式编写,但会保存每个子问题的解,通常用数组或散列表存储,当需要返回一个子问题的解时,首先检查是否已经保存过这个解,如果是,则直接返回,不是,则用通常方法计算这个解。

  • 自底向上法

这种方法保证了在求解每一个子问题时,规模比他小的相关子问题都已经被求解,只需要通过数组或者散列表查询到所需子问题的值,而不需要递归调用。

目前看来,对于《算法导论》上的钢条切割问题,原问题即所选规模最大问题,不需要做相应变形;而本题需要将规模最大问题变形为找长度为n的序列的和最大数组,且最后一个元素包含在和最大数组中。对于钢条切割问题,最大规模问题的解就是我们最终要求的最优解,而对于本题,最大规模问题的解不一定是原问题的最优解,所以需要用一个值来动态存储目前的到的最优解。但他们的共同特点均是需要保存每个子问题的解,免于重复计算。

上述问题均是要求最优解的值,如果需要得到最优解本身,则需要在执行过程中维护一些额外的信息。以下是C++代码:

class Solution {public:    int maxSubArray(vector<int>& nums) {        int n=nums.size();        int *DP=new int[n];        DP[0]=nums[0];        int i;        int maxA=DP[0];        for(i=1;i<n;i++){            DP[i]=(DP[i-1]>0)?DP[i-1]+nums[i]:nums[i];            maxA=max(maxA,DP[i]);        }        return maxA;    }};
代码中用DP[n]存储每个子问题的最优解,用maxA动态保存目前为止产生的最优解的值。由于只有一个循环,所以算法复杂度为O(n)。



原创粉丝点击