LeetCode刷题记录----53.最大子数组和(meidum)

2025/8/16

题目(meidum):


我的思路:

1.暴力遍历

因为我们需要求的是最大的子数组和,因此我们可以考虑最简单粗暴的方式就是把所有子数组的和都求出来,然后逐个对比找出其中的最大值

代码如下:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        #暴力算法, 把所有子数组的和都求出来然后比较
        res = float('-inf')

        def arraySum(start, end):
            sum = 0
            for i in range(start, end):
                sum += nums[i]
            return sum

        n = len(nums)
        for i in range(0, n):
            for j in range(i+1, n+1):
                res = max(res, arraySum(i, j))

        return res

时间复杂度:O(N^{3})

空间复杂度:O(1)

这个时间复杂度高的离谱,用进去包超时的。不过我们其实可以考虑给他进行一点小小的优化。比如这里我们每次计算两个索引位置的子区间的和的时候我们都要重复遍历一次,其实我们可以先预处理一下,用一个数组prefix存储每个索引位置的前缀和。这样如果我们要求的区间是[i, j],那它的数组和就是prefix[j] - prefix[i]

具体代码如下:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        #稍微优化暴力解法
        #计算每个索引位置的前缀和
        n = len(nums)
        prefix = [0] * (n+1)
        for i in range(n):
            prefix[i+1] = prefix[i] + nums[i]
        
        #然后用前缀和快速计算每个子数组的和
        maxSum = nums[0]
        for i in range(n):
            for j in range(i+1, n+1):
                maxSum = max(maxSum, prefix[j] - prefix[i])
        
        return maxSum

时间复杂度:O(N^{2})

空间复杂度:O(N)

不过遗憾的是,这样好像依然超时了。所以我们得继续想其他的办法

2.动态规划

我们还可以采用动态规划的思想来做这个题。对于每个索引位置i,  我们对动态规划状态dp[i]可以定义为:以该索引结尾的子数组的最大和。而显然这个最大和有两种情况,要么是之前的数一直加到这个位置,要么是它自身。

即动态转移方程为:dp[i] = max(dp[i-1] + nums[i], nums[i])

这个通俗点说就是:如果当前这个位置的数比它的前缀和还大的话,那肯定只能是它自身就是最大子数组和了;否则肯定是它的前缀和更大

而我们只需要得到这个动态规划数组中最大的值即可。当然我们还可以进一步优化处理让它不必用数据去存储每一个索引位置的动态规划值

具体步骤如下:

①声明两个初始变量curSum,maxSum,初始值为nums[0]。【一个用于记录当前索引位置为右端点的数组的和,一个用于记录全局最大的数组和】

②遍历数组,并且根据动态转移方程每次更新curSum,然后更新maxSum

③返回masSum

具体代码如下:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        #动态规划
        n = len(nums)
        curSum = nums[0]    #表示当前元素结尾的最大子数组和(初始为第一个元素)
        maxSum = nums[0]    #表示整体最大子数组和(初始为第一个元素)
        for i in range(1, n):
            curSum = max(curSum + nums[i], nums[i])
            maxSum = max(maxSum, curSum)
        
        return maxSum

时间复杂度:O(N)

空间复杂度:O(1)

这下总算不会超时了。


其他思路:

官方题解还提供了一种叫做分治法的算法。我看了一下大概我理解的思路是:

 ①每个区间对应四个状态量:

  • iSum:区间和
  • lSum:包含左端点的最大子区间和
  • rSum:包含右端点的最大子区间和
  • mSum:最大子区间和

②而这个状态量又由该区间的左右子区间决定

③因此由上述两个条件可以知道,要求主区间的状态量,就要分别求出左右子区间的状态量然后合并即可。而左右子区间的状态量也可以这样求得(很明显的递归的意味)

具体的代码与注释如下:

class Status:
    def __init__(self, lSum, rSum, mSum, iSum):
        self.lSum = lSum    #包含区间左端点的最大字段和
        self.rSum = rSum    #包含区间右端点的最大字段和
        self.mSum = mSum    #区间内最大字段和
        self.iSum = iSum    #区间字段和

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        #分治法

        #获取该区间的四个状态量信息
        def getInfo(nums, l, r):
            #递归终止条件
            if l == r:
                return Status(nums[l], nums[l], nums[l], nums[l])

            #计算左右子区间的状态,该区间的状态量是由左右子区间的状态量决定的
            mid = (l+r)//2
            lState = getInfo(nums, l, mid)  
            rState = getInfo(nums, mid+1, r)

            #把计算得到的状态进行合并处理
            return push(lState, rState)

        #合并两个区间的状态量
        def push(l, r):
            iSum = l.iSum + r.iSum      #区间字段和等于做有子区间的字段和
            lSum = max(l.lSum, l.iSum + r.lSum)   #包含左端点的区间最大字段和 是(左子区间的lSum 和 左子区间的iSum+右子区间的lSum)的最大值
            rSum = max(r.rSum, r.iSum + l.rSum)   #和lSum的定义类似
            mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum)   #跨过mid和没跨过mid两种情况

            return Status(lSum, rSum, mSum, iSum)

        n = len(nums)
        #传入数组,以及左右区间长度划分的子区间,得到其状态中的mSum
        return getInfo(nums, 0, n-1).mSum

时间复杂度:O(log2N)

空间复杂度:O(N)

虽然说是log2N但是运行测试的时候似乎不如动态规划的方式快


总结:

①对于数组的求和,前缀和可以是一种很直接的预处理方式

②对于动态地求数组的最大的值,可以考虑用动态规划的思想(也算贪心)

③分治法主要是通过把区间多次分为两段去考察它们分离的状态量与合并的状态量的区别与使用来求解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萘柰奈

谢谢老板喵

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值