给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。
样例
给出数组[−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));
}
};