算法训练营 Day27 - 贪心算法part01

理论基础

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

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

455.分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:

  • 输入: g = [1,2,3], s = [1,1]
  • 输出: 1 解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以你应该输出 1。

示例 2:

  • 输入: g = [1,2], s = [1,2,3]
  • 输出: 2
  • 解释:你有两个孩子和三块小饼干,2 个孩子的胃口值分别是 1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2.

提示:

  • 1 <= g.length <= 3 * 10^4
  • 0 <= s.length <= 3 * 10^4
  • 1 <= g[i], s[j] <= 2^31 - 1
python 复制代码
class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()  # 先排序孩子胃口
        s.sort()  # 先排序饼干大小
        
        child_i = 0  # 孩子的指针
        cookie_j = 0 # 饼干的指针
        
        # 只有当 既有孩子 又有饼干 时才继续
        while child_i < len(g) and cookie_j < len(s):
            # 如果当前饼干能满足当前孩子
            if s[cookie_j] >= g[child_i]:
                child_i += 1 # 孩子满足了,看下一个孩子
            
            # 无论是否满足,饼干都要往后移
            # (如果满足了,这块饼干被吃了,要看下一块)
            # (如果不满足,这块太小了,要看下一块更大的)
            cookie_j += 1
            
        return child_i # 满足的孩子数量就是 child_i 的下标

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

示例 1:

  • 输入: [1,7,4,9,2,5]
  • 输出: 6
  • 解释: 整个序列均为摆动序列。

示例 2:

  • 输入: [1,17,5,10,13,15,10,5,16,8]
  • 输出: 7
  • 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。

示例 3:

  • 输入: [1,2,3,4,5,6,7,8,9]
  • 输出: 2

贪心算法 - "数峰值"

其实这道题最直观的解法就是:数山峰和山谷

想象把数组画成折线图,我们只需要统计拐点的数量。

  • 如果是单调递增(1, 2, 3, 4),中间的数都没用,只要两头。

  • 一旦趋势发生改变(从升变降,或从降变升),序列长度就 +1。

python 复制代码
class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        if len(nums) < 2:
            return len(nums)
        
        # cur_diff: 当前坡度 (现在的数 - 前一个数)
        # pre_diff: 前一个坡度
        pre_diff = 0
        result = 1  # 默认序列最右边的一个数算作一个长度,所以从1开始
        
        for i in range(len(nums) - 1):
            # 计算当前一对数的坡度
            cur_diff = nums[i+1] - nums[i]
            
            # 核心判断:出现"拐点"
            # 情况1:之前是平的或下的(pre<=0),现在往上了(cur>0) -> 形成山谷
            # 情况2:之前是平的或上的(pre>=0),现在往下了(cur<0) -> 形成山峰
            if (cur_diff > 0 and pre_diff <= 0) or (cur_diff < 0 and pre_diff >= 0):
                result += 1
                pre_diff = cur_diff # 更新前一个坡度,准备找下一个拐点
                
        return result

假设输入是 [1, 17, 5, 10, 13, 15, 10]

  1. 1 -> 17 :上升(cur > 0),之前默认 pre=0是一个拐点(起点)result 变成 2。记录 pre 为正。

  2. 17 -> 5 :下降(cur < 0),之前是正。是拐点(山峰)result 变成 3。记录 pre 为负。

  3. 5 -> 10 :上升(cur > 0),之前是负。是拐点(山谷)result 变成 4。记录 pre 为正。

  4. 10 -> 13 :上升。之前也是正。不是拐点,只是半山腰,跳过。

  5. 13 -> 15 :上升。之前也是正。不是拐点,跳过。

  6. 15 -> 10 :下降。之前是正。是拐点(山峰)result 变成 5。

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
python 复制代码
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res = nums[0]
        total = 0
        for n in nums:
            if total < 0:
                total = 0
            total += n
            res = max(res,total)
        return res

为什么要 if total < 0: total = 0?为什么一旦当前和小于 0 就要扔掉?

记忆故事:【赌徒的口袋】

想象你在赌场走一条路,路上全是筹码。

  • 有些是正数(捡到钱)。

  • 有些是负数(遇到抢劫,要倒扣钱)。

你的任务是:找一段路,让你手里的钱最多。

  • 逻辑推演

    • 你捡到了一个 -2(当前 total = -2)。

    • 这时候前面有个 4 等着你。

    • 如果你带着之前的 -2 去捡这个 4,你手里的总数是 2

    • 如果你扔掉 之前的 -2,空手去捡这个 4,你手里的总数是 4

    • 显然,之前的 -2 是个累赘!

核心口诀:

"只要之前的收益是负债(Total < 0),就立刻破产重开(Total = 0),不要拖累下一个数。"

因为任何数加上一个负数,都会变小。所以只要当前的"累积和"变成了负数,它对未来就没有任何贡献了,直接丢弃,从现在重新开始算。

那些 updown 到底在算什么?为什么要看坡度?

记忆故事:【炒股只做波段】

把你给的数组想象成股票的K线图

  • [1, 17, 5, 10, 13, 15, 10]

  • 我们要找最长的摆动序列,其实就是问你:这只股票一共发生了几次趋势逆转?

逻辑推演:

  1. 1 -> 17(涨了) :好,现在是上涨趋势。中间如果变成 1 -> 5 -> 17 这种微小波动不管,反正只要最后比前面高就行。这时候我们处于山峰寻找中。

  2. 17 -> 5(跌了) :趋势变了!从涨变跌,这说明刚才的 17 是个山顶(拐点)。记录下来,现在开始找山谷。

  3. 5 -> 10(涨了) :趋势又变了!说明刚才的 5 是个谷底(拐点)。记录下来。

  4. 10 -> 13 -> 15(一直涨) :这叫单边行情 。你在 10 买入,在 15 卖出是一样的,中间的 13 是废话,不算摆动。直到它下次跌了,才算一个新的摆动。

核心口诀:

"只抓拐点,过滤单边。" 只有当坡度反向(从上变下,或从下变上)时,长度才 +1。单调递增或递减的一段路,只算作一步。

不要背代码,要背物理模型

  1. 最大子序和 = 带负债就扔(负数对于以此开头的子序列是累赘)。

  2. 摆动序列 = 数山峰山谷(只在趋势改变时计数)。

  3. 分发饼干 = 田忌赛马/以大吃大(最大的饼干给最贪心的孩子,或者最小的饼干给最小胃口的孩子,不要浪费)。

相关推荐
CoovallyAIHub12 小时前
Moonshine:比 Whisper 快 100 倍的端侧语音识别神器,Star 6.6K!
深度学习·算法·计算机视觉
CoovallyAIHub13 小时前
速度暴涨10倍、成本暴降6倍!Mercury 2用扩散取代自回归,重新定义LLM推理速度
深度学习·算法·计算机视觉
CoovallyAIHub13 小时前
实时视觉AI智能体框架来了!Vision Agents 狂揽7K Star,延迟低至30ms,YOLO+Gemini实时联动!
算法·架构·github
CoovallyAIHub14 小时前
开源:YOLO最强对手?D-FINE目标检测与实例分割框架深度解析
人工智能·算法·github
CoovallyAIHub14 小时前
OpenClaw:从“19万星标”到“行业封杀”,这只“赛博龙虾”究竟触动了谁的神经?
算法·架构·github
刀法如飞14 小时前
程序员必须知道的核心算法思想
算法·编程开发·算法思想
徐小夕16 小时前
pxcharts Ultra V2.3更新:多维表一键导出 PDF,渲染兼容性拉满!
vue.js·算法·github
CoovallyAIHub17 小时前
OpenClaw一脚踩碎传统CV?机器终于不再只是看世界
深度学习·算法·计算机视觉
CoovallyAIHub17 小时前
仅凭单目相机实现3D锥桶定位?UNet-RKNet破解自动驾驶锥桶检测难题
深度学习·算法·计算机视觉