首先还是看题:
给你一个整数数组 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
- 题目分析:题目的重点是连续子数组,即要求是不排序的。
- 解题方法:动态规划。
- 了解动态规划 为什么能想到动态规划(以下简称动归)?以及动归解决哪些类的问题?
-
为什么会想到动态规划?通常是因为你在解决问题时发现了一些重叠子问题,或者问题可以被分解成子问题,并且子问题之间存在某种递归关系。动态规划是一种非常强大的解决方法,可以有效地处理这些类型的问题,并且通常能够提供高效的算法。
-
最优化问题:动态规划常用于解决最优化问题,例如最短路径问题、最长递增子序列、背包问题、最大子数组和等。在这些问题中,你需要找到最佳的决策或最优的解决方案。
-
具有重叠子问题性质的问题:动态规划通常用于具有重叠子问题性质的问题,即问题可以被分解成相似的子问题,这些子问题会多次出现在解决过程中。通过记忆化或动态规划表格,你可以避免不必要的重复计算。
-
具有最优子结构性质的问题:最优子结构性质意味着问题的最优解可以通过子问题的最优解组合而成。这种性质使得你可以逐步构建问题的最优解。
-
问题的解可以通过状态转移方程描述:动态规划通常用于问题的解可以通过状态之间的递推关系(状态转移方程)来描述的情况。状态转移方程定义了如何从小问题的解构建出大问题的解。
- 动归具体有哪些步骤?
状态转移方程是动态规划问题的核心部分,它定义了问题的状态之间的关系,用于描述如何从一个状态转移到另一个状态。状态转移方程通常以递归或迭代的方式定义,并且是动态规划问题的关键步骤之一。
状态转移方程通常包括以下几个要素:
-
状态定义:你需要明确定义问题中的状态。这些状态是问题的局部信息,它们一起描述了问题的整体情况。
-
状态之间的关系:状态转移方程描述了一个状态如何从其他状态转移而来。这个关系通常是问题的核心逻辑,它指导了如何从小问题的解构建出大问题的解。
-
初始状态:你需要确定问题的初始状态,也就是问题的最小子问题的解。通常,初始状态是已知的或可以直接计算的。
-
边界条件:在状态转移方程中,你需要定义问题的边界条件,即不能再分解的情况,通常是问题的基本情况。这些条件用于终止递归或循环。
现在回归到当前的题目当中,解题步骤参考leetcode K哥,下为链接
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
状态定义:设动归列表dp,dp[i] 代表以元素 nums 【i】 为结尾的连续子数组的最大和。
为何定义最大和dp【i】中必须包含元素nums【i】?
保证dp【i】递推到dp【i+1】的正确性;如果不包含nums【i】,递推时则不满足连续子数组的要求。
转移方程:若dp【i-1】<=0 , 说明dp[i-1] 对dp【i】 产生负贡献, 即dp【i-1】+nums【i】还不如本身大。
初始状态:dp【0】 = nums【0】,即以nums【0】结尾的连续子数组最大和为nums【0】。
返回值:返回dp列表中的最大值,代表全局最大值。
状态压缩:
- 由于dp【i】只与dp【i-1】和nums【i】有关系,因此可以将原数组nums用作dp列表,在nums上修改就行。
- 省去dp列表使用的额外空间,因此空间复杂度从O(N)降至O(1)。
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
// dp[i] 表示:以nums[i]结尾的连续子数组的最大和
int[] dp = new int[len];
dp[0] = nums[0];
int max = nums[0];
// 主要状态转移方程
for(int i=1 ;i<len;i++){
if(dp[i-1]>0){
dp[i] = dp[i-1] + nums[i];
} else{
dp[i] = nums[i];
}
max = Math.max(max,dp[i]);
}
return max ;
}
}