编号:0003
昨天出去玩,少写了一道,今天补上
题目来源:leetcode
题目描述
给定一个整数数组nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
示例
输入:[-2,-1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组[4,-1,2,1]
的和最大,为6
题解
自己没想出来,直接看的官方题解
官方题解传送门
官方题解方法有二,动态规划,分治法
动态规划
思路分析
动态规划是将整个数组归纳考虑,假设我们已经知道了以第i-1
个数结尾的连续子数组的最大和
i
个数结尾的
连续子数组的最大和的可能取值要么为
nums[i]
单独成一段,也就是
nums[i]
,在这两个数中我们取最大值,也就是说
有因为显然
因为我们在计算
pre
保存
代码
代码如下(我按照官方题解的思路自己写的代码,很麻烦):
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int max, pre;
max = nums[0];
pre = nums[0];
for(int i = 1; i< nums.size(); i++)
{
if(pre + nums[i] > nums[i])
pre = pre+nums[i];
else
pre = nums[i];
if(pre > max)
max = pre;
}
return max;
}
};
代码如下(官方代码):
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = 0, maxAns = nums[0];
for (const auto &x: nums) {
pre = max(pre + x, x);
maxAns = max(maxAns, pre);
}
return maxAns;
}
};
提交结果
执行用时:8ms,击败74.51%
内存消耗:6.9MB 击败100%
算法复杂度分析
时间复杂度:整个过程中只是对数组进行一遍遍历,每次遍历的时候进行两次max
比较操作,因此时间复杂度
空间复杂度:值设置了两个变量pre
,max
因此空间消耗为常数,空间复杂度为
分治法
思路分析
分治法是将问题进行拆分,此题我们处理的问题是从[l,r]
区间中找出最大字段和,那么在拆分区间的时候,可以以其中间值mid=(l+r)/2
作为分治求解的划分。
对于区间中,我们想要了解的内容有这些
lSum
表示[l,r]
中以l
为左端点的最大子段和rSum
表示[l,r]
中以r
为右端点的最大子段和mSum
表示[l,r]
内的最大子段和iSum
表示[l,r]
的区间和
首先知道当划分到l==r
的问题是可以简单求解的,此时lSum = rSum = mSum = iSum = nums[l]
那么接下来要知道怎么求解合并后的区间的这四个量
首先iSum
最好求解,只要把[l,mid]
的iSum
加上[mid,r]
的iSum
就很容易求得
然后当求解rSum
的时候,它有两种可能,第一种就是[mid,r]
的rSum
,符合以r
为右端点的要求,第二个就是[l,mid]
的rSum
加上[mid,r]
的iSum
,这两段显然是连续的,同时又符合以r
为右端点。求其最大值赋给rSum
,就好
求解lSum
同上,也有两种可能,(1)[l,mid]
的lSum
,(2)[l,mid]
的iSum
加上[mid,r]
的lSum
求解mSum
,若不跨越中间值mid
,那么显然其取值就是左区间的mSum
和右区间的mSum
进行比较。如果跨越了,那就是左子区间的lSum
加上右子区间的的rSum
,三者取最大
代码
代码如下(官方代码):
class Solution {
public:
struct Status {
int lSum, rSum, mSum, iSum;
};
Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum;
int lSum = max(l.lSum, l.iSum + r.lSum);
int rSum = max(r.rSum, r.iSum + l.rSum);
int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);
return (Status) {lSum, rSum, mSum, iSum};
};
Status get(vector<int> &a, int l, int r) {
if (l == r) return (Status) {a[l], a[l], a[l], a[l]};
int m = (l + r) >> 1;
Status lSub = get(a, l, m);
Status rSub = get(a, m + 1, r);
return pushUp(lSub, rSub);
}
int maxSubArray(vector<int>& nums) {
return get(nums, 0, nums.size() - 1).mSum;
}
};
执行结果
执行用时:4ms 击败97.65%
内存消耗:7.1MB 击败100%
复杂度分析
时间复杂度:我们这里相当于对二叉树的所有结点进行遍历,最终遍历时间为,因此时间复杂度为
空间复杂度:遍历过程中,递归使用的栈,时间复杂度
暴力法
传送门
思路分析
就是求解出其所有子序然后分别求和,找出其中的最大值就好
代码
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
int max = INT_MIN;
int numsSize = int(nums.size());
for (int i = 0; i < numsSize; i++)
{
int sum = 0;
for (int j = i; j < numsSize; j++)
{
sum += nums[j];
if (sum > max)
max = sum;
}
}
return max;
}
};
执行结果
执行用时:600ms 击败5.06%
内存消耗:7.1MB 击败100%
复杂度分析
时间复杂度,整个过程中,每一次对i
的遍历,都进行了次比较,因此最终的操作次数为,为
空间复杂度:只定义了两个变量,max
和numsSize
,因此空间复杂度为
贪心法
思路分析
这里利用的其实和动态规划有些类似,动态规划比较的是
sum
存储前面的
nums
之和,只有当
sum
小于零的时候,才把
sum
清零,从下一位置开始进行重新求和相加。
代码如下
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = INT_MIN;
int numsSize = int(nums.size());
int sum = 0;
for (int i = 0; i < numsSize; i++)
{
sum += nums[i];
result = max(result, sum);
//如果sum < 0,重新开始找子序串
if (sum < 0)
{
sum = 0;
}
}
return result;
}
};
提交结果
执行用时:8ms,击败74.51%
内存消耗:6.9MB,击败100%
复杂度分析
时间复杂度,整个过程中对数组进行一次遍历,时间复杂度
空间复杂度:只设置了result,numsSize,sum
常数个变量,空间复杂度