「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」
题目
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
提示:
- 1 <= nums.length <= 105
- -104 <= nums[i] <= 104
进阶: 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
解题思路
方法一:暴力出奇迹
用两次for循环暴力列举出所有情况。
但是很不幸,在leetcode中提交会发现此中方法超时。
时间复杂度O(n^2)
/**
* @param {number[]} nums
* @return {number}
*/
const maxSubArray = (nums) => {
let sum = 0, max = -Infinity;
for(let i=0; i<nums.length; i++){
sum = 0;
for(let j=i; j<nums.length; j++){
sum += nums[j];
max = Math.max(max, sum);
}
}
return max;
};
方法二:动态规划
-
动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
-
如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
-
如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
-
每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果
-
时间复杂度:O(n)
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
let ans = nums[0];
let sum = 0;
for(const num of nums) {
if(sum > 0) {
sum += num;
} else {
sum = num;
}
ans = Math.max(ans, sum);
}
return ans;
};
方法三:分治法
分治法的做题思路是:先将问题分解为子问题;解决子问题后,再将子问题合并,解决主问题。
使用分治法解本题的思路是:
将数组分为 2 部分。例如 [1, 2, 3, 4] 被分为 [1, 2] 和 [3, 4] 通过递归计算,得到左右两部分的最大子序列和是 lsum,rsum 从数组中间开始向两边计算最大子序列和 cross 返回 max(lsum, cross, rsum) 整体过程可以参考来自 Leetcode 官方题解的图:
可以看到,分治法可行的关键的是:最大子序列和只可能出现在左子数组、右子数组或横跨左右子数组 这三种情况下。
代码实现如下:
/**
* @param {number[]} nums
* @param {number} left
* @param {number} right
* @param {number} mid
* @return {number}
*/
function crossSum(nums, left, right, mid) {
if (left === right) {
return nums[left];
}
let leftMaxSum = Number.MIN_SAFE_INTEGER;
let leftSum = 0;
for (let i = mid; i >= left; --i) {
leftSum += nums[i];
leftMaxSum = Math.max(leftMaxSum, leftSum);
}
let rightMaxSum = Number.MIN_SAFE_INTEGER;
let rightSum = 0;
for (let i = mid + 1; i <= right; ++i) {
rightSum += nums[i];
rightMaxSum = Math.max(rightMaxSum, rightSum);
}
return leftMaxSum + rightMaxSum;
}
/**
* @param {number[]} nums
* @param {number} left
* @param {number} right
* @return {number}
*/
function __maxSubArray(nums, left, right) {
if (left === right) {
return nums[left];
}
const mid = Math.floor((left + right) / 2);
const lsum = __maxSubArray(nums, left, mid);
const rsum = __maxSubArray(nums, mid + 1, right);
const cross = crossSum(nums, left, right, mid);
return Math.max(lsum, rsum, cross);
}
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
return __maxSubArray(nums, 0, nums.length - 1);
};
-
时间复杂度是O(NlogN)
-
由于递归调用,所以空间复杂度是O(logN)
方法四:贪心法
在循环中找到不断找到当前最优的和 sum。
注意:sum 是 nums[i] 和 sum + nums[i]中最大的值。这种做法保证了 sum 是一直是针对连续数组算和。
代码实现如下:
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
let maxSum = (sum = nums[0]);
for (let i = 1; i < nums.length; ++i) {
sum = Math.max(nums[i], sum + nums[i]);
maxSum = Math.max(maxSum, sum);
}
return maxSum;
};
-
时间复杂度:O(1)
-
空间复杂度:O(n)
结束语
只想感叹一句,大家说的最简单的一道动态规划,对于小葵来说真的好难啊 😭😭
硬着头皮看了好几遍题解才勉强看懂。
这里把最全的解题方法分享给大家。
这里是小葵🌻,只要把心朝着太阳的地方,就会有温暖~
让我们一起来攻克算法难关吧!!