您的位置:首页 > 其它

Leetcode53——Maximum Subarray

2017-03-14 17:18 302 查看
文章作者:Tyan

博客:noahsnail.com | CSDN | 简书

本文主要是对最大子数组(序列)问题求解的学习与总结,最大子数组问题是一道经典的算法题,这道题解法有很多,因此可以学习到很多求解问题的思路,并可以学习到算法的优化过程。

1. 问题描述

英文:

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.

中文:

主要是给定一个数组,求解数组的子数组中,数组元素和最大的那一个子数组,返回的是最大子数组的和。

2. 求解

解法一

最简单也是最容易想到的思路就是三层循环,对
(i,j),i<=j
的情况进行遍历,这种情况下的算法复杂度为O(n3)。代码如下:

public class Solution {
public int maxSubArray(int[] nums) {
//如果需要节省空间,可将n替换
int n = nums.length;
int max = nums[0];
for(int i = 0; i < n; i++) {
for(int j = i; j < n; j++){
int sum = 0;
//注意k的边界,存在i=j的情况
for(int k = i; k <= j; k++) {
sum += nums[k];
if(sum > max) {
max = sum;
}
}
}
}
return max;
}
}


Leetcode上的运行结果如下:



解法二

从Leetcode结果可以看出,时间超时了,O(n3)的时间复杂度确实太高了,需要进行优化。分析上面的代码,在i不变的情况下,j每增加1,其和都是在上次求和基础上加上最新的元素,而在第三层循环中都是重新从i开始计算求和,因此存在数据冗余(求和的重复计算),因此需要需要去掉算法中的冗余部分。这种情况下的代码复杂度变为O(n2),代码如下:

public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int max = nums[0];
for(int i = 0; i < n; i++) {
int sum = 0;
for(int j = i; j < n; j++){
sum += nums[j];
if(sum > max) {
max = sum;
}
}
}
return max;
}
}


Leetcode上运行结果如下:



解法三

从Leetcode结果可以看出,时间还是超时了,但从执行的测试数据数量上来看,比第一次多执行了两个,但在最后一个测试数据上时间超时了。那么能不能有进一步的优化呢?答案是肯定有的。可以使用分治法来求解,算法复杂度为O(nlogn),但是其实本题并不适合使用分治法,太复杂。虽然算法复杂度降低了一些,因此这里略过分治法,直接寻找更优解法。

解法四

还有没有更好的方法呢?答案也是肯定的。首先假设存在最大子数组X,则最大子数组X中的任意一个子数组x都不应该为负数,如果x为负数,则X必定不是最大子数组(可用反证法证明)。根据这个思想,我们只需要以此累加数组元素,并将和与0比较,如果小于0,则需要在剩下的元素中重新寻找是否存在最大子数组,如果不小于0,则与保存的最大子数组值进行比较,如果大于最大子数组值,则更新最大子数组值。这样只需要一次遍历就能找到最大子数组,这种解法的算法复杂度为O(n)。根据这个思路,解决这个问题的算法复杂度代码如下:

public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int max = nums[0];
int sum = 0;
for(int i = 0; i < n; i++) {
sum += nums[i];
if(sum > max) {
max = sum;
}
if(sum < 0) {
sum = 0;
}
}
return max;
}
}


Leetcode通过了。

解法五

还有没有别的方法呢?答案还是肯定的。使用动态规划求解。动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。 使用动态规划求解问题,最重要的就是确定动态规划三要素:(1)问题的阶段;(2)每个阶段的状态;(3)从前一个阶段转化到后一个阶段之间的递推关系。

1.起始阶段
(i=0)
max = nums[0]
;2.第
i(i > 0)
个阶段,
max = curMax[i]
curMax
是第i个阶段的最大子序列和;3.第
i-1
和第
i
个阶段的关系,
curMax[i] = Math.max(curMax[i - 1] + nums[i], nums[i])
;4.根据前面动态规划的定义,则最大子序列和
max = Math.max(max, curMax[i])


public class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
//curMax是当前的最大子序列和
int[] curMax = new int
;
curMax[0] = nums[0];
int max = nums[0];
for (int i = 1; i < n; i ++) {
curMax[i] = Math.max(curMax[i - 1] + nums[i], nums[i]);
max = Math.max(max, curMax[i]);
}
return max;
}
}


Leetcode通过了。

分析解法四与解法五

其实解法四与解法五是一致的,解法四中的sum等于解法五中的curMax[i],解法五中如果
curMax[i-1]
小于0,则
curMax[i] = nums[i]
,而在解法四中由于第
i-1
次时
sum=curMax[i-1]
,因此需要将sum重置为0,则
sum + nums[i] = nums[i]
,与
curMax[i] = nums[i]
是一致的。如果解法五中
curMax[i-1]
大于等于0,则curMax[i] =
curMax[i - 1] + nums[i]
,此时方法四中
sum = sum + nums[i]
。而第
i-1
次时,
sum = curMax[i - 1]
,两者也是等价的。解法五中的curMax[0]替换为sum,curMax[i]替换为sum,将
curMax[i] = Math.max(curMax[i - 1] + nums[i], nums[i]);
变换为
sum  += nums[i];
if(sum < 0) {   sum = 0; }
,即可将代码从解法五变换为解法四的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: