目录
53. 最大子数组和
问题描述
给你一个整数数组 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
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
进阶:如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
思路与算法1
可以肯定的是,满足这样条件的子数组(称为最大和区间)肯定是夹在两个负数或0(不包含)之间的。
进一步,从该区间的左端从右向左看,以一个0或者负数为起点到下一个0或者负数(不包含)为界的区间作为一个单位区间,区间和的累计和不会出现大于0的情况。否则的话将该最大和区间向左延申可以找到更大和的区间,这样就矛盾了。
同理,从该区间的右端从左向右看,以一个0或者负数为起点到下一个0或者负数(不包含)为界的区间作为一个单位区间,区间和的累计和不会出现大于0的情况。
因此,可以分别从两端出发扫描,确定最大和区间的两端边界。
首先,从左向右扫描。每碰到一个负数或0,计算到该数之前的区间的总和,如果该总和比之前的更大则更新最大和,并将右边界更新为该数的索引值。最终得到的即为最终的最大和区间的右边界(不包含)。
同样, 从右向左扫描。每碰到一个负数或0,计算到该数之前的区间的总和,如果该总和比之前的更大则更新最大和,并将 左边界 更新为该数的索引值。最终得到的 即为最终的最大和区间的左边界(不包含)。
以下以示例1的数据[-2,1,-3,4,-1,2,1,-5,4]为例进行说明。搜索过程如下图所示:
由此可以得到最大和区间在 之间(不包含两端边界)。
以上思路看起来可行,然而,实际coding完一测试,才发现所作的前提是错误的。当数组中很多负数的情况下事情就不对了。。。不过毕竟是一种思路,留作纪念。说不定这一思路以后解决别的问题能够用得上。
思路与算法2:动态规划
假设 数组的长度是 n,下标从 0 到 n-1。
我们用 代表以第 i 个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是:
因此我们只需要求出每个位置的 ,然后返回 所得f 数组中的最大值即可。那么我们如何求 呢?我们可以考虑 单独成为一段还是加入 对应的那一段,这取决于 和 的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:
由此不难给出一个时间复杂度 O(n)、空间复杂度 O(n) 的实现,即用一个 f 数组来保存 的值,用一个循环求出所有。考虑到 只和 相关,于是我们可以只用一个变量 来维护对于当前 的 的值是多少,从而让空间复杂度降低到 O(1),这有点类似「滚动数组」的思想。
以上思路来自官解,参考:https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-by-leetcode-solution/
from typing import List
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
maxsum = nums[0]
prev = nums[0]
for i in range(1,len(nums)):
cur = max(prev+nums[i], nums[i])
maxsum = max(cur,maxsum)
prev = cur
return maxsum
if __name__ == "__main__":
sln = Solution()
nums = [-2,1,-3,4,-1,2,1,-5,4]
print(sln.maxSubArray(nums))
nums = [1]
print(sln.maxSubArray(nums))
nums = [5,4,-1,7,8]
print(sln.maxSubArray(nums))
执行用时:200 ms, 在所有 Python3 提交中击败了34.59%的用户
内存消耗:25.5 MB, 在所有 Python3 提交中击败了85.07%的用户
官解还给出了基于分治的解题方法,不过瞄了一眼觉得好复杂,暂且免了。
918. 环形子数组的最大和
问题描述
给定一个长度为 n
的环形整数数组 nums
,返回 nums
的非空 子数组 的最大可能和 。
环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i]
的下一个元素是 nums[(i + 1) % n]
, nums[i]
的前一个元素是 nums[(i - 1 + n) % n]
。
子数组 最多只能包含固定缓冲区 nums
中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j]
,不存在 i <= k1, k2 <= j
其中 k1 % n == k2 % n
。
示例 1:
输入:nums = [1,-2,3,-2] 输出:3 解释:从子数组 [3] 得到最大和 3
示例 2:
输入:nums = [5,-3,5] 输出:10 解释:从子数组 [5,5] 得到最大和 5 + 5 = 10
示例 3:
输入:nums = [3,-2,2,-3] 输出:3 解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3
提示:
n == nums.length
1 <= n <= 3 * 10^4
-3 * 10^4 <= nums[i] <= 3 * 10^4
思路与算法
考虑最大和子区间的位置可以分为两种情况。
第一种是不跨越首尾边界,这种情况的求解与上一题相同。记这种条件下求得的最大和为maxsum1。
第二种是跨越了首尾边界。跨越了首尾边界的某个区间的和是最大的话,那就意味着互补区间(这个一定不跨越首尾边界)的和一定是最小子区间和。所以,以非环形的方式求出最小区间和,用总和减掉这个最小区间和即得跨越首尾边界的最大区间和maxsum2。
取maxsum1和maxsum2中的较大者即得最终答案。
记以索引结尾的子区间最小和为,我们所要求的就是。考虑从到的状态转移方程,可以得到(思路与上一题求最大区间和相同):
但是有一种例外情况需要考虑,就是输入数组全为非正数时,此时最小区间和就是全区间,总和减去最小区间和就是0了,此时对应的最大和区间是空区间,而空区间是不满足条件的。这种情况下直接返回第一步求得的最大区间和即可。
代码
from typing import List
class Solution:
def maxSubarraySumCircular(self, nums: List[int]) -> int:
# Find the maximum subarray sum
maxsum = nums[0]
prev = nums[0]
for i in range(1,len(nums)):
cur = max(prev+nums[i], nums[i])
maxsum = max(cur,maxsum)
prev = cur
# Find the minimum subarray sum
minsum = nums[0]
prev = nums[0]
for i in range(1,len(nums)):
cur = min(prev+nums[i], nums[i])
minsum = min(cur,minsum)
prev = cur
print(maxsum, minsum)
if minsum == sum(nums):
return maxsum
else:
return max(maxsum, sum(nums)-minsum)
if __name__ == "__main__":
sln = Solution()
nums = [1,-2,3,-2]
print(sln.maxSubarraySumCircular(nums))
nums = [5,-3,5]
print(sln.maxSubarraySumCircular(nums))
nums = [3,-2,2,-3]
print(sln.maxSubarraySumCircular(nums))
nums = [1,-2,1-2]
print(sln.maxSubarraySumCircular(nums))
nums = [-3,-2,-3]
print(sln.maxSubarraySumCircular(nums))
执行用时:288 ms, 在所有 Python3 提交中击败了13.38%的用户
内存消耗:18.4 MB, 在所有 Python3 提交中击败了36.49%的用户
以上两个for-loop可以合并为一个。
class Solution:
def maxSubarraySumCircular(self, nums: List[int]) -> int:
# Find the maximum subarray sum
maxsum = nums[0]
prevMax = nums[0]
minsum = nums[0]
prevMin = nums[0]
for i in range(1,len(nums)):
curMax = max(prevMax+nums[i], nums[i])
maxsum = max(curMax,maxsum)
prevMax = curMax
curMin = min(prevMin+nums[i], nums[i])
minsum = min(curMin,minsum)
prevMin = curMin
maxsum2 = sum(nums)-minsum
if maxsum2 == 0:
return maxsum
else:
return max(maxsum, maxsum2)
官解关于本题给出了四种解法(看不懂,不搬运了^-^):
方法 1:邻接数组
方法 2:前缀和 + 单调队列
方法 3:Kadane 算法(符号变种)
方法 4:Kadane 算法(最小值变种)