代码随想录算法训练营第三十一天 | 贪心算法理论基础、455. 分发饼干、376. 摆动序列、53. 最大子数组和

64 阅读3分钟

贪心算法理论基础

什么是贪心

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

贪心的套路(什么时候用贪心)

  • 说实话贪心算法并没有固定的套路
  • 最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧
  • 刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心
  • 因为贪心有时候就是常识性的推导,所以会认为本应该就这么做!

贪心一般解题步骤

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

455. 分发饼干

代码随想录文章讲解

贪心(排序后比较)

  • 使得喂给每个孩子的是能够满足他胃口的最小的饼干
  • 想清楚局部最优,想清楚全局最优,感觉局部最优是可以推出全局最优,并想不出反例,那么就试一试贪心
class Solution:
    # 思路1:优先考虑喂饼干
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()
        s.sort()
        res = 0
        for i in range(len(s)):
            if res <len(g) and s[i] >= g[res]:  #小饼干先喂饱小胃口
                res += 1
        return res

376. 摆动序列

代码随想录文章讲解

贪心算法

  • 局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值
  • 整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列
  • 即不断寻找下个符合条件的值,并记录
# Time complexity: O(n)
# Space complexity: O(1)
class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        pre, cur, res = 0, 0, 1
        for i in range(len(nums) - 1):
            cur = nums[i + 1] - nums[i]
            # cur and pre are in different signs
            if cur * pre <= 0 and cur != 0:
                res += 1
                pre = cur
        return res

动态规划

很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即nums[i] > nums[i-1]),要么是作为山谷(即nums[i] < nums[i - 1])。

  • 设dp状态dp[i][0],表示考虑前i个数,第i个数作为山峰的摆动子序列的最长长度
  • 设dp状态dp[i][1],表示考虑前i个数,第i个数作为山谷的摆动子序列的最长长度

则转移方程为:

  • dp[i][0] = max(dp[i][0], dp[j][1] + 1),其中0 < j < inums[j] < nums[i],表示将nums[i]接到前面某个山谷后面,作为山峰。
  • dp[i][1] = max(dp[i][1], dp[j][0] + 1),其中0 < j < inums[j] > nums[i],表示将nums[i]接到前面某个山峰后面,作为山谷。

初始状态:

由于一个数可以接到前面的某个数后面,也可以以自身为子序列的起点,所以初始状态为:dp[0][0] = dp[0][1] = 1

# Time complexity: O(n^2)
# Space complexity: O(n)
class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        dp = []
        
        for i in range(len(nums)):
            dp.append([1, 1])
            for j in range(i):
                if nums[j] < nums[i]:
                    # nums[j] as the previous down, or do not take nums[j] into account
                    dp[i][0] = max(dp[j][1] + 1, dp[i][0])
                if nums[j] > nums[i]:
                    # nums[j] as the previous up, or do not take nums[j] into account
                    dp[i][1] = max(dp[j][0] + 1, dp[i][1])
        
        return max(dp[-1][0], dp[-1][1])

53. 最大子数组和

代码随想录文章讲解

贪心

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。

全局最优:选取最大“连续和”

局部最优的情况下,并记录最大的“连续和”,可以推出全局最优

# Time complexity: O(n)
# Space complexity: O(1)
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res = float('-inf')
        cur = 0
        
        for num in nums:
            cur += num
            if cur > res:
                res = cur
            # plus a negative number always make the result become smaller
            if cur < 0:
                cur = 0
        
        return res

动态规划

# Time complexity: O(n)
# Space complexity: O(n)
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        dp = [0] * len(nums)
        dp[0] = nums[0]
        res = dp[0]
        
        for i in range(1, len(nums)):
            # either continue the subarray or start a new subarry with nums[i]
            dp[i] = max(nums[i], dp[i-1] + nums[i])
            res = max(res, dp[i])
                
        return res