最大子序和
给你一个整数数组 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) 的解法,尝试使用更为精妙的 分治法 求解。
相关标签
数组
分治
动态规划
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xn3cg3/
这道题跟呢个买股票的最佳时机是同一类型的题,当然都用到了动态规划,这里是动态规划的详细解答。也可以有暴力求解法,分治法和贪心算法。这里我会把4种方法都写一遍,并且记录为什么会有这类算法。
分析一下最为关键的一点,nums = [-2,1,-3,4,-1,2,1,-5,4],我们何时才能算作子数组是合适的。当我们加起来的连续子数组是大于0的就说明这是正确的.
1.暴力求解
使用两层for循环,对每一次的值进行相加,更新max值
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
int max = INT_MIN;
for(int i = 0;i<nums.size();i++){
int sums = 0;
for(int j = i;j<nums.size();j++){
sums = sums + nums[j];
if(sums>max){
max = sums;
}
}
}
return max;
}
};
当然这种在leetcode是运行不起来的,超出了时间限制,但是这是一种方法,他相当于将所有的字串的值都扫描了出来,但是他的问题就是,很多以及计算过的数值,接下来我们还要再一次的计算。时间复杂度O(n^2)
2.动态规划
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = INT_MIN;
//因为只需要知道dp的前一项,我们用int代替一维数组
int dp(nums[0]);
result = dp;
for (int i = 1; i < nums.size(); i++)
{
dp = max(dp + nums[i], nums[i]);
result = max(result, dp);
}
return result;
}
};
首先代码如上。时间复杂度O(n)
因为我看了很多关于这道题动态规划的解释我还是感觉不明所以,并且我用手推算过一边还是不明所以。这里摘出维基百科的解释
// Kadane算法扫描一次整个数列的所有数值,
// 在每一个扫描点计算以该点数值为结束点的子数列的最大和(正数和)。
// 该子数列由两部分组成:以前一个位置为结束点的最大子数列、该位置的数值。
// 因为该算法用到了“最佳子结构”(以每个位置为终点的最大子数列都是基于其前一位置的最大子数列计算得出,
// 该算法可看成动态规划的一个例子。
// 状态转移方程:sum[i] = max{sum[i-1]+a[i],a[i]}
// 其中(sum[i]记录以a[i]为子序列末端的最大序子列连续和)
直到这里,我才明白,当res的值保存的是前面的子项和的最值,dp保存的最大子串以谁结尾的,或者是当前最大字串的值。然后当你走到2步的时候,你的字串还是-2,1这里,因为这是你当前最大的字串,当走到4,你的当前字串就变成了4.这也是他最重要的思想,从结束点作为起点来看待问题。
3.贪心算法
贪心算法其实就是为了找寻最大最小值,当我们的子串累加如果大于0就继续累加如果小于0就将他等于0,然后继续累加。这里摘下别人的代码
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;
}
};
作者:pinku-2
链接:https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-cshi-xian-si-chong-jie-fa-bao-li-f/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
result这里存放的是最大的值,这样的好处是,如果你前面的就是最大值,但是因为中间有的字串小于0让其断开,我们也能很好的保存最大值。时间复杂度o(n)
这里无需担心如果整个字符串都是负数,那我们这个是不是没用,因为我们在判断前,就一直result去维护我们的最大值,不管你是否判断,我们都会有result来获取最大值的操作。
4.分治算法
分治算法是将大问题化解为子问题,而对于这道题的子问题,要么最大子串的和在中心的左边,或右边,或者就在中心。
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
//类似寻找最大最小值的题目,初始值一定要定义成理论上的最小最大值
int result = INT_MIN;
int numsSize = int(nums.size());
result = maxSubArrayHelper(nums, 0, numsSize - 1);
return result;
}
int maxSubArrayHelper(vector<int> &nums, int left, int right)
{
if (left == right)
{
return nums[left];
}
int mid = (left + right) / 2;
int leftSum = maxSubArrayHelper(nums, left, mid);
//注意这里应是mid + 1,否则left + 1 = right时,会无线循环
int rightSum = maxSubArrayHelper(nums, mid + 1, right);
int midSum = findMaxCrossingSubarray(nums, left, mid, right);
int result = max(leftSum, rightSum);
result = max(result, midSum);
return result;
}
int findMaxCrossingSubarray(vector<int> &nums, int left, int mid, int right)
{
int leftSum = INT_MIN;
int sum = 0;
for (int i = mid; i >= left; i--)
{
sum += nums[i];
leftSum = max(leftSum, sum);
}
int rightSum = INT_MIN;
sum = 0;
//注意这里i = mid + 1,避免重复用到nums[i]
for (int i = mid + 1; i <= right; i++)
{
sum += nums[i];
rightSum = max(rightSum, sum);
}
return (leftSum + rightSum);
}
};
作者:pinku-2
链接:https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-cshi-xian-si-chong-jie-fa-bao-li-f/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。