给你一个整数数组 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 <= 105
-104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
动态规划:
解决该问题的 Python 代码,时间复杂度为 O(n),空间复杂度为 O(1):
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 当前最大和
curr_max = 0
# 全局最大和
max_so_far = float('-inf')
for num in nums:
# 更新当前最大和为当前元素或当前元素加上之前的当前最大和
curr_max = max(num, curr_max + num)
# 更新全局最大和
max_so_far = max(max_so_far, curr_max)
return max_so_far
以下是对上述动态规划思路的详细分析:
- 我们定义两个变量
curr_max
和max_so_far
。 curr_max
表示以当前位置元素为结尾的子数组的最大和。在遍历过程中,对于每个元素num
,我们有两种选择:要么单独以这个元素作为新的子数组开始(即curr_max = num
),要么将这个元素添加到前面已经计算的当前最大和子数组后面(即curr_max = curr_max + num
),我们取这两种情况中的较大值来更新curr_max
。- 而
max_so_far
则是用来记录到目前为止所见到的最大子数组和。在每次更新完curr_max
后,我们将其与max_so_far
进行比较,取较大值更新max_so_far
,这样就能保证始终记录的是全局的最大和。 - 遍历整个数组,通过不断动态更新这两个值,最终得到的
max_so_far
就是要求的具有最大和的连续子数组的和。
时间复杂度为 O(n),因为只需要对数组进行一次遍历。
空间复杂度为 O(1),只使用了固定的额外空间来存储当前最大和以及全局最大和等少量变量。
分治法:
基本思路是将数组不断分成左右两部分,分别计算左右部分的最大子数组和以及跨越中间的最大子数组和,然后取三者中的最大值。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
def divide_and_conquer(nums, l, r): # 定义分治函数,参数为数组及左右边界
if l == r: # 如果左右边界相等,即只有一个元素
return nums[l]
mid = (l + r) // 2 # 计算中间位置
left_max = divide_and_conquer(nums, l, mid) # 递归计算左半部分最大子数组和
right_max = divide_and_conquer(nums, mid + 1, r) # 递归计算右半部分最大子数组和
cross_max = self.cross_max(nums, l, mid, r) # 计算跨越中间的最大子数组和
return max(left_max, right_max, cross_max) # 返回三者中的最大值
def cross_max(nums, l, mid, r): # 计算跨越中间的最大子数组和的函数
left_sum = float('-inf')
left_curr = 0
for i in range(mid, l - 1, -1): # 从中间向左计算最大和
left_curr += nums[i]
left_sum = max(left_sum, left_curr)
right_sum = float('-inf')
right_curr = 0
for i in range(mid + 1, r + 1): # 从中间向右计算最大和
right_curr += nums[i]
right_sum = max(right_sum, right_curr)
return left_sum + right_sum # 返回跨越中间的最大和
return divide_and_conquer(nums, 0, len(nums) - 1) # 调用分治函数开始计算
时间复杂度在平均情况下为 O(nlogn),在最坏情况下为 O(n^2)。分治法将问题不断分成两半,每层的计算量大致为 n,总共会有 logn 层,所以平均情况是 O(nlogn),但如果数据分布特殊,可能会退化为 O(n^2)。
空间复杂度主要是递归调用栈的空间,为 O(logn)。
两种方法比较:
在这种情况下,通常动态规划是更好的选择。
动态规划具有以下优点:
- 时间复杂度稳定:始终为 O(n),无论输入数据的具体情况如何。
- 相对简单直观:逻辑比较清晰,易于理解和实现。
而分治法虽然在某些问题上有其独特优势,但在该问题中:
- 平均时间复杂度虽然是 O(nlogn),但可能存在特殊情况导致退化,不如动态规划稳定。
- 实现起来相对复杂一些。
当然,具体问题还需要具体分析,不同的方法可能在不同的场景和约束条件下各有优劣。但就该连续子数组最大和问题而言,动态规划在实际应用中更为常用和高效。