Leetcode基础算法-贪心算法


贪心算法简介

1 贪心算法的定义

贪心算法(Greedy Algorithm)是一种算法策略,它在每个决策步骤中总是选择当前情况下最优的选择,以期望最终得到一个全局最优解。它通过将问题分解成若干个步骤,在每一步选择局部最优解,从而希望能推导出全局最优解。

例子:找零问题

假设要用硬币找零,目标是用尽可能少的硬币来组成特定金额。我们使用的硬币面额为:1元、2元和5元。

问题描述:你需要找零7元。

贪心策略

  1. 每次选择面额最大的硬币。
  2. 从7元开始,首先选择5元,然后剩余2元。
  3. 剩下的2元可以用两枚1元硬币或者一枚2元硬币。

具体步骤

  1. 初始金额:7元
  2. 选择5元硬币:使用1枚5元,剩余2元
  3. 选择2元硬币:使用1枚2元

结果:总共使用了2枚硬币(1枚5元和1枚2元)。

这个例子中,贪心算法通过每一步选择最大的面额来减少总的硬币数量。选择5元硬币是局部最优选择,因为它能迅速减少剩余金额,并在之后也很容易处理剩下的金额。

2 贪心算法的特征

贪心算法并不总是适用,只有满足以下两个条件时,它才会给出全局最优解:

  1. 贪心选择性质:问题的全局最优解可以通过一系列局部最优解(贪心选择)来获得。
  2. 最优子结构性质:问题的最优解包含其子问题的最优解。

3 贪心算法正确性的证明

证明贪心算法正确性的常用方法有:

  • 数学归纳法:验证边界情况,并证明对于每个情况,当前解可由前一个解推导得出。
  • 交换论证法:通过交换某些元素而不改变全局最优性,来证明当前解是最优解。

4 贪心算法三步走

  1. 转换问题:将优化问题转换为具有贪心选择性质的问题。
  2. 贪心选择:根据问题选择合适的度量标准,制定贪心策略。
  3. 最优子结构:结合局部最优解与子问题的最优解,得到原问题的最优解。

5 举个栗子

一位家长为孩子们分发饼干的问题

给定两个数组:

  • g:代表孩子们的胃口值,g[i]表示第i个孩子的最低饼干尺寸需求。
  • s:代表饼干的尺寸,s[j]表示第j块饼干的大小。

目标:最大化能够得到满足的孩子数量,前提是每块饼干只能分配给一个孩子。

示例
  • 示例 1

    • 输入:g = [1, 2, 3], s = [1, 1]
    • 输出:1(只有胃口为1的孩子能得到满足)
  • 示例 2

    • 输入:g = [1, 2], s = [1, 2, 3]
    • 输出:2(两个孩子都能满足)

解题思路

贪心算法

核心思想:尽量让胃口小的孩子得到小块饼干,留出更大的饼干给胃口大的孩子。

三步走的方法
  1. 转换问题:首先对孩子的胃口和饼干的尺寸分别进行排序。每当满足当前胃口最小的孩子后,再处理剩下的孩子。

  2. 贪心选择:针对每个孩子,选择最小的能满足其胃口的饼干。

  3. 最优子结构:当前孩子的贪心选择加上剩余孩子的最优解,构成整体的最优解。

代码实现

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()  # 对孩子的胃口进行排序
        s.sort()  # 对饼干的尺寸进行排序
        
        index_g, index_s = 0, 0  # 初始化索引
        res = 0  # 初始化结果
        
        while index_g < len(g) and index_s < len(s):
            if g[index_g] <= s[index_s]:  # 如果当前饼干可以满足当前孩子
                res += 1  # 满足孩子数量加1
                index_g += 1  # 移动到下一个孩子
            index_s += 1  # 无论如何都移动到下一个饼干

        return res  # 返回满足孩子的最大数量

复杂度分析

  • 时间复杂度O(m * log m + n * log n),其中 mn 分别是数组 gs 的长度,排序操作为主。
  • 空间复杂度O(log m + log n),主要是由于排序过程中使用的额外空间。

1710. 卡车上的最大单元数

问题描述

给定一个二维数组 boxTypes,其中 boxTypes[i] = [numberOfBoxesi, numberOfUnitsPerBoxi] 表示:

  • numberOfBoxesi 是类型 i 的箱子的数量。
  • numberOfUnitsPerBoxi 是类型 i 每个箱子可以装载的单元数量。

你还有一个整数 truckSize,表示卡车上可以装载箱子的最大数量。目标是返回卡车可以装载的单元的最大总数。

示例
  • 示例 1

    • 输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4
    • 输出:8
    • 解释:可以选择所有第一类和第二类的箱子,以及第三类的一个箱子,总单元数为 8
  • 示例 2

    • 输入:boxTypes = [[5,10],[2,5],[4,7],[3,9]], truckSize = 10
    • 输出:91

解题思路

贪心算法

核心思想:为了最大化单元数量,优先选择单位单元数最多的箱子,直到达到卡车的容量限制。

具体步骤
  1. 排序:根据 numberOfUnitsPerBoxi 从大到小排序 boxTypes
  2. 装箱:遍历排序后的数组,尽可能装载箱子,直到 truckSize 用完。
    • 对于每种类型的箱子:
      • 如果当前类型的箱子数量小于或等于剩余的 truckSize,则全部装入。
      • 否则,只装入剩余的 truckSize 个箱子。

代码实现

class Solution:
    def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int:
        # 按每箱子单位数量降序排序
        boxTypes.sort(key=lambda x: x[1], reverse=True)
        
        total_units = 0
        
        for numberOfBoxes, unitsPerBox in boxTypes:
            if truckSize <= 0:  # 如果卡车已经装满
                break
            
            # 计算可以装入的箱子数量
            boxes_to_load = min(numberOfBoxes, truckSize)
            total_units += boxes_to_load * unitsPerBox  # 增加总单元数
            truckSize -= boxes_to_load  # 减少卡车可用空间

        return total_units

复杂度分析

  • 时间复杂度O(n log n),其中 n 是 boxTypes 的长度,主要用于排序。
  • 空间复杂度O(1),只使用常数空间存储变量。

总结

贪心算法是一种解决优化问题的策略,其核心思想是在每一步选择中都选择当前看起来最优的选项,期望通过局部最优的选择达到全局最优的结果。以下是贪心算法的主要特点和应用:

  • 简单直观:贪心算法通过简单的选择步骤来构建解决方案,易于理解和实现。例如,在选择物品时,总是选择价值最高的物品。

  • 局部最优:在每一步,贪心算法只考虑当前的最优选择,而不关注未来可能的选择。这使得算法快速而高效,但并不适用于所有问题。

  • 适用场景:贪心算法适合解决一些特定类型的问题,如最小生成树、最短路径、分配问题等。这些问题具有“贪心选择性质”和“最优子结构”,即当前的最优选择能引导到整体的最优解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值