1、 题目描述
LeetCode 53.最大子数组和
😉给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
2、思路
思路历程
一眼动态规划!为什么呢?
动态规划的定义:全局最优可通过组合子问题的最优解得到,说人话就是全局的最优解可以通过一步步的局部最优解累积得到。
带入本题,全局的最大子数组和其实就是
-
首先计算以第一个数组元素为边界的子数组最大值 sum1
-
然后计算以第二个数组元素为边界的子数组最大值 sum2
-
然后计算以第三个数组元素为边界的子数组最大值 sum3
-
。。。
-
然后计算以第n个数组元素为边界的子数组最大值 sum n
得到动态规划递推逻辑:
最大子数组和 = 到前一个索引位的最大子数组和 + 当前索引位的元素,或者是当前索引位元素,,通俗来讲就是 选不选当前索引位元素,因为对于当前元素来讲 只有选与不选的可能,我们比较的就是选后大 、还是不选后大
动态规划递推公式:
dp[ i ] = Math.max ( dp[ i - 1 ] + num[ i ] , num[ i ] )
其中 i 表示 第 i 个数组索引位的 子数组和最大值是多少。
动态规划的本质:以一定的规则通过局部求解,从而慢慢逻辑推理出全局的解
举例说明:
前两个元素最大字数组和 = 前一个元素的最大子数组和 与选、不选 第二个元素比较大小
sum1 = Math.max ( sum0 + num1 , num1 )
前三个元素最大子数组和 = 前两个元素最大字数组和 与选、不选 第三个元素比较大小
sum2 = Math.max ( sum1 + num2 , num2 )
3、代码
class Solution {
public int maxSubArray(int[] nums) {
// 动态规划 dp 数组
int[] dp = new int[nums.length];
// 初始化第一个元素,此时表示 索引位为0的最大子数组和为 nums[0]
dp[0] = nums[0];
// 存储全局最大子数组和
int globalMax = nums[0];
// 由于初始化了0,所以从1号位开始遍历计算
for(int i = 1; i < nums.length; i++){
// 递推公式的应用
dp[i] = Math.max(dp[i-1] + nums[i],nums[i]);
// 比较得到当前的最大子数组和
globalMax = Math.max(dp[i],globalMax);
}
return globalMax;
}
}
4、算法分析
复杂度分析
时间复杂度: 只遍历了一遍数组,总时间复杂度为 O(n)
空间复杂度: 创建了与输入数组相同的数组,总空间复杂度O(n)。还有优化空间
实际执行耗时与内存占用率:
5、总结
动态规划是一种常见的解题思路,掌握简单的题解是重要的。简单的来说动态规划就是找到 递推公式,代码实现递推公式。大多数动态规划都可以归类为 选 还是 不选的逻辑,以便于理解。
6、tips
上述实现的代码中 ,为了更清晰的讲清楚动态规划,所以按照动态规划的模版模式 1.定义dp数组 ,2.从小到大推理dp数组值,3.得到全局解。对于上述题目来讲 dp数组实际只用到了 当前元素的前一个值(增加了堆空间内存使用),频繁调用 max方法(增加了栈空间内存使用),频繁比较、赋值globalMax (增加了时间复杂度)。
那么总结下来还有三个优化点:
-
只需要一个变量记住前一个索引位数组最大值就行
-
减少方法调用
-
短路比较与更新
代码实现
class Solution {
public int maxSubArray(int[] nums) {
// 全局最大子数组和
int globalMax = nums[0];
// 前一个元素最大子数组和
int preIndexMax = nums[0];
for (int i = 1; i < nums.length; i++) {
preIndexMax = preIndexMax < 0 ? nums[i] : preIndexMax + nums[i];
// 短路更新
if(preIndexMax > globalMax){
globalMax = preIndexMax;
}
}
return globalMax;
}
}
建议关注同名微信公众号 以便及时收到最新更新~