题目链接:https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/
1. 题目介绍(42. 连续子数组的最大和)
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
【测试用例】:
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
【条件约束】:
提示:
- 1 <= arr.length <= 10^5
- -100 <= arr[i] <= 100
【相关题目】:
注意:本题与主站 【LeetCode】No.53. 最大子数组和 – Java Version 题目相同。
2. 题解
2.1 枚举 – O(n2)
时间复杂度O(n2),空间复杂度O(1)
【解题思路】:
遇事不决,暴力枚举,最直接的方法,枚举数组的所有子数组
并求出它们的和。不过不知道是不是最近干力活干多了,我一开始想的是 1. 先分出所有子数组;2. 遍历并比较所有子数组,找出最大。如果题目要求的是 返回所有符合条件的子数组
这类的使用这种方法还是比较合理,如果只是像这道题一样,返回仅仅是最大值,这就属实没有必要。
……
【实现策略】:
- 定义一个双重循环,循环遍历每一种子数组的可能;
- 定义当前累加和变量
sum
以及最大和maxSum
,一旦发现当前累加和大于了最大和,就修改最大和为累加和;- 循环结束,返回结果。
class Solution {
// Soulution1:枚举所有子数组和
public int maxSubArray(int[] nums) {
// 定义变量 n 记录数组长度
int n = nums.length;
// 定义变量 maxSum 记录最大子数组和
int maxSum = Integer.MIN_VALUE;
// 双重循环,依次遍历所有可能的子数组
for (int i = 0; i < n; i++)
{
int sum = 0;
for (int j = i; j < n; j++)
{
sum = sum + nums[j];
// 如果当前子数组的和大于 maxSum,则让其成为最大
if (sum > maxSum)
{
maxSum = sum;
}
}
}
// 最后返回结果
return maxSum;
}
}
2.2 举例分析数组的规律(原书题解1)-- O(n)
时间复杂度O(n),空间复杂度O(1)
【解题思路】:
试着从头到尾逐个累加示例数组中的每个数字,初始化和为 0。第一步加上第一个数字 1,此时和为 1。第二部加上数字 -2,和就变成了 -1。第三步加上数字 3,我们注意到由于此前累加的和是 -1,小于 0,那么如果用 -1 加上3,得到的和是 2,比 3 本身还要小。因此,我们不用考虑从第一个数字开始的子数组,之前累加的和也被抛弃。
通过举例,我们就能找到如下规律:
- 从第一个元素 遍历累加开始,如果遇到 累加和小于等于0 的情况(遇到负数,减小了累加和),说明此时的子数组必然不可能是最大,可将其抛弃;
- 抛弃前面的累加和后,遍历到的当前值重新出发,直到遍历结束,得出最大和。
……
【实现策略】:
- 如解题思路所示,一次遍历,当当前累加和
sum
小于等于 0时,抛弃前累加和,仅将其赋值为当前数字;- 如果累加和大于0,则让其继续累加;
- 在遍历的过程中,一旦发现当前累加和大于了最大和
maxSum
,就修改最大和的值为当前累加和。
class Solution {
// Soulution2:一次遍历
public int maxSubArray(int[] nums) {
// 判断无效输入
if (nums.length <= 0) return -1;
// 定义变量 n 记录数组长度
int n = nums.length;
// 定义累加和 sum 与 最大和 maxSum
int sum = 0;
int maxSum = Integer.MIN_VALUE;
// 开始循环
for (int i = 0; i < n; i++){
// 如果当前 累加和 小于等于0,丢弃该值,重新开始
if (sum <= 0) sum = nums[i];
else sum += nums[i];
// 如果在循环的过程中出现了更大值,则修改最大和
if (sum > maxSum) maxSum = sum;
}
return maxSum;
}
}
2.3 动态规划(原书题解2)-- O(n)
时间复杂度O(n),空间复杂度O(1)
【解题思路】:
关于什么时候用DP?
- 首先你要了解
回溯法
,毕竟是DP
的起源,在你熟悉 回溯 之后,你发现这道题它好像可以用 回溯 去做,或者发现你想遍历它来给出答案,但是答案要求的并不是把所有满足的解法都列出来,而是只需要给出一共有几种解法,这时候一般就可以考虑使用DP
。……
如上图公式所示:
- 我们可以用函数
f(i)
来表示以第i
个数字结尾的子数组的最大和,那么我们需要求出max[f(i)]
,其中0 <= i < n
.;- 当以第
i-1
个数字结尾的子数组的数字和小于0时,把这个负数与第i
个数累加,得到的结果会比第i
个数字本身还要小,所以这种情况下以第i
个数字结尾的子数组就是第i
个数字本身;- 如果以第
i-1
个数字结尾的子数组的数字和大于0,则与第i
个数字累加就得到以第i
个数字结尾的子数组中所有数字的和。……
【实现策略】:
动态规划属于后一状态依赖前一状态的值,在此处,dp[i]
仅与dp[i-1]
有关。在本题中使用动态规划的思想解决该题,代码和 2.2 几乎一致,其实说白了就是找关系,只不过难点在于如何找到 从前一个状态转化到后一个状态之间的递推关系。
class Solution {
public int maxSubArray(int[] nums) {
int count = nums.length;
int sum = nums[0],dp=nums[0];
for(int i = 1; i < count; i++)
{
if(dp > 0) dp += nums[i];
else dp = nums[i];
sum = Math.max(sum,dp);
}
return sum;
}
}
3. 参考资料
[1] 面试题42. 连续子数组的最大和(动态规划,清晰图解)
[2] 如果第一时间想到dp的话1分钟写完
[3] 动态规划算法入门