文章目录
贪心算法简介
1 贪心算法的定义
贪心算法(Greedy Algorithm)是一种算法策略,它在每个决策步骤中总是选择当前情况下最优的选择,以期望最终得到一个全局最优解。它通过将问题分解成若干个步骤,在每一步选择局部最优解,从而希望能推导出全局最优解。
例子:找零问题
假设要用硬币找零,目标是用尽可能少的硬币来组成特定金额。我们使用的硬币面额为:1元、2元和5元。
问题描述:你需要找零7元。
贪心策略:
- 每次选择面额最大的硬币。
- 从7元开始,首先选择5元,然后剩余2元。
- 剩下的2元可以用两枚1元硬币或者一枚2元硬币。
具体步骤:
- 初始金额:7元
- 选择5元硬币:使用1枚5元,剩余2元
- 选择2元硬币:使用1枚2元
结果:总共使用了2枚硬币(1枚5元和1枚2元)。
这个例子中,贪心算法通过每一步选择最大的面额来减少总的硬币数量。选择5元硬币是局部最优选择,因为它能迅速减少剩余金额,并在之后也很容易处理剩下的金额。
2 贪心算法的特征
贪心算法并不总是适用,只有满足以下两个条件时,它才会给出全局最优解:
- 贪心选择性质:问题的全局最优解可以通过一系列局部最优解(贪心选择)来获得。
- 最优子结构性质:问题的最优解包含其子问题的最优解。
3 贪心算法正确性的证明
证明贪心算法正确性的常用方法有:
- 数学归纳法:验证边界情况,并证明对于每个情况,当前解可由前一个解推导得出。
- 交换论证法:通过交换某些元素而不改变全局最优性,来证明当前解是最优解。
4 贪心算法三步走
- 转换问题:将优化问题转换为具有贪心选择性质的问题。
- 贪心选择:根据问题选择合适的度量标准,制定贪心策略。
- 最优子结构:结合局部最优解与子问题的最优解,得到原问题的最优解。
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
(两个孩子都能满足)
- 输入:
解题思路
贪心算法
核心思想:尽量让胃口小的孩子得到小块饼干,留出更大的饼干给胃口大的孩子。
三步走的方法
-
转换问题:首先对孩子的胃口和饼干的尺寸分别进行排序。每当满足当前胃口最小的孩子后,再处理剩下的孩子。
-
贪心选择:针对每个孩子,选择最小的能满足其胃口的饼干。
-
最优子结构:当前孩子的贪心选择加上剩余孩子的最优解,构成整体的最优解。
代码实现
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)
,其中m
和n
分别是数组g
和s
的长度,排序操作为主。 - 空间复杂度:
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
- 输入:
解题思路
贪心算法
核心思想:为了最大化单元数量,优先选择单位单元数最多的箱子,直到达到卡车的容量限制。
具体步骤
- 排序:根据
numberOfUnitsPerBoxi
从大到小排序boxTypes
。 - 装箱:遍历排序后的数组,尽可能装载箱子,直到
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)
,只使用常数空间存储变量。
总结
贪心算法是一种解决优化问题的策略,其核心思想是在每一步选择中都选择当前看起来最优的选项,期望通过局部最优的选择达到全局最优的结果。以下是贪心算法的主要特点和应用:
-
简单直观:贪心算法通过简单的选择步骤来构建解决方案,易于理解和实现。例如,在选择物品时,总是选择价值最高的物品。
-
局部最优:在每一步,贪心算法只考虑当前的最优选择,而不关注未来可能的选择。这使得算法快速而高效,但并不适用于所有问题。
-
适用场景:贪心算法适合解决一些特定类型的问题,如最小生成树、最短路径、分配问题等。这些问题具有“贪心选择性质”和“最优子结构”,即当前的最优选择能引导到整体的最优解。