「这是我参与11月更文挑战的第 6 天,活动详情查看:2021最后一次更文挑战」
刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能,更能锻炼思维
连续子数组的最大和
题目来源:LeetCode-剑指 Offer 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
解题
解法一:暴力解法
思路
暴力解法的思路很简单,只要我求出来所有的子序列的和,然后取最大的那个就可以了。直接上代码
代码
//暴力解法
func MaxSubArray2(nums []int) int {
n := len(nums)
if n == 0 {
return -1
}
if n==1 {
return nums[0]
}
max := nums[0]
for i := 0; i < n-1; i++ {
sum := 0
for j := i; j < n; j++ {
sum += nums[j]
if sum > max {
max = sum
}
}
}
return max
}
解法二:动态规划解法
思路
一般看到求最优解的问题,都会想到贪心算法和动态规划。而贪心算法并不能总是给出最优解,所以这道题很容易想到用动态规划来解题
什么样的题适合用动态规划来解?动态规划能解决的问题有什么规律可循?
总结为:一个模型三个特征
一个模型:多阶段决策最优解模型
一般是用动态规划来解决最优问题。而解决问题的过程,需要经历多个决策阶段。每个决策阶段都对应着一组状态。然后我们寻找一组决策序列,经过这组决策序列,能够产生最终期望求解的最优值
三个特征
- 最优子结构 最优子结构指的是,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。也可以理解为,后面阶段的状态可以通过前面阶段的状态推导出来
- 无后效性 无后效性有两层含义,第一层含义是,在推导后面阶段的状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步一步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性
- 重复子问题 不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态
了解了适合用动态规划解决的问题的特点,可以看一下本题,它首先是一个求最优解的问题(不是说所有的最优解问题都用动态规划来解,只是说它应该在我们的考虑范围之内)
本题要求的是连续子数组的最大值,看能不能套用动态规划里边的最优子结构这一特征。假设要求0 ~ 5这段下标的连续数据的最大和,那只要知道下标0 ~ 4这段连续数组的最大和其实就可以了,为什么?
假设0~4这段连续子数组的最大和为n,如果n+arr[5] > arr[5],那n + arr[5]不就是0 ~ 5这段下标的连续数据的最大和吗?所以,其实就是可以根据前边的最优解,推出后边这个的最优解
所以,假设数组长度是n。那我推出下标01的最大连续子序列的和,也就是知道了下标02的最大连续子序列的和,直到0~n
有了上边这个思路,代码就很好写了
代码
// 连续子数组的最大和
func MaxSubArray(nums []int) int {
n := len(nums)
if n == 0 {
return -1
}
if n==1 {
return nums[0]
}
maxSum := nums[0]
for i:=1; i < n; i++ {
if nums[i] + nums[i-1] > nums[i] {
nums[i] += nums[i-1] //注意,这就将num[i]位置的值,保存成了0~i这个子序列的最大和(相当于记录了0~i这段连续子数组的状态(最大和))
}
if nums[i] > maxSum {
maxSum = nums[i]
}
}
return maxSum
}