贪心算法详情分析

一、贪心算法核心知识点

1. 什么是贪心算法?

定义 :在每一步选择中都采取当前状态下最好或最优的选择,从而希望导致结果是全局最优的算法。

通俗理解

  • 就像吃自助餐,你每次都拿最贵的菜,希望最后吃回本

  • 就像找零钱,每次都拿最大面额的纸币

2. 贪心算法的基本要素

要素 说明
贪心选择性质 每一步的局部最优选择能导致全局最优解
最优子结构 问题的最优解包含子问题的最优解
无后效性 当前决策不影响后续决策的独立性

3. 贪心 vs 动态规划

对比项 贪心算法 动态规划
决策方式 每一步只做当前最优 考虑所有可能,选全局最优
是否回溯 不回溯 需要回溯
时间复杂度 通常较低 通常较高
适用条件 贪心选择性质 + 最优子结构 最优子结构 + 重叠子问题

4. 贪心算法的步骤

text

复制代码
1. 将问题分解为若干个子问题
2. 找出贪心策略(怎么选才是当前最优)
3. 证明贪心策略的正确性(最难)
4. 实现算法

二、经典贪心算法题目及代码

题目1:找零钱问题(LeetCode 322 变种)

问题:有面额 1, 2, 5, 10, 20, 50, 100 的纸币,用最少的张数凑出金额 n。

贪心策略:优先使用大面额纸币

python

复制代码
def coin_change(coins, amount):
    """
    贪心找零(前提:货币系统是标准的,能用贪心)
    coins: 面额列表,已从大到小排序
    amount: 需要凑的金额
    返回:需要的纸币张数
    """
    coins.sort(reverse=True)  # 从大到小排序
    count = 0
    remaining = amount
    
    for coin in coins:
        if remaining == 0:
            break
        # 能用几张当前面额的纸币
        num = remaining // coin
        count += num
        remaining -= num * coin
        print(f"使用 {num} 张 {coin} 元")
    
    if remaining != 0:
        return -1  # 无法凑出
    return count

# 测试
coins = [100, 50, 20, 10, 5, 2, 1]
amount = 188
result = coin_change(coins, amount)
print(f"最少需要 {result} 张纸币")
# 输出:使用 1 张 100元,1张50元,1张20元,1张10元,1张5元,1张2元,1张1元

题目2:活动选择问题

问题:给定多个活动的开始和结束时间,选择最多的互不冲突的活动。

贪心策略 :优先选择结束时间最早的活动

python

复制代码
def activity_selection(activities):
    """
    活动选择问题
    activities: [(开始时间, 结束时间), ...]
    返回:最多能选择的活动数量
    """
    if not activities:
        return 0
    
    # 按结束时间排序
    activities.sort(key=lambda x: x[1])
    
    count = 1  # 选择第一个活动
    last_end = activities[0][1]  # 第一个活动的结束时间
    
    selected = [activities[0]]  # 记录选中的活动
    
    for start, end in activities[1:]:
        if start >= last_end:  # 下一个活动的开始 >= 上一个活动的结束
            count += 1
            last_end = end
            selected.append((start, end))
    
    print("选中的活动:", selected)
    return count

# 测试
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
result = activity_selection(activities)
print(f"最多能安排 {result} 个活动")

题目3:分发饼干(LeetCode 455)

问题:每个孩子最多只能给一块饼干。每个孩子 i 有一个胃口值 gi,每块饼干 j 有尺寸 sj。当 sj >= gi 时,孩子满足。最多能满足多少个孩子?

贪心策略 :用最小尺寸 的饼干去满足最小胃口的孩子

python

复制代码
def find_content_children(g, s):
    """
    分发饼干
    g: 孩子的胃口值列表
    s: 饼干尺寸列表
    返回:最多能满足的孩子数量
    """
    g.sort()
    s.sort()
    
    child = 0  # 孩子指针
    cookie = 0  # 饼干指针
    count = 0
    
    while child < len(g) and cookie < len(s):
        if s[cookie] >= g[child]:
            # 这块饼干能满足这个孩子
            count += 1
            child += 1
            cookie += 1
        else:
            # 这块饼干太小,换一块更大的
            cookie += 1
    
    return count

# 测试
g = [1, 2, 3]  # 孩子的胃口
s = [1, 1]     # 饼干尺寸
print(f"能满足 {find_content_children(g, s)} 个孩子")  # 输出: 1

题目4:跳跃游戏(LeetCode 55)

问题:给定一个非负整数数组 nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。

贪心策略 :维护能到达的最远位置

python

复制代码
def can_jump(nums):
    """
    跳跃游戏
    返回:是否能到达最后一个位置
    """
    max_reach = 0  # 当前能到达的最远位置
    n = len(nums)
    
    for i in range(n):
        # 如果当前位置已经超过了能到达的最远位置
        if i > max_reach:
            return False
        
        # 更新能到达的最远位置
        max_reach = max(max_reach, i + nums[i])
        
        # 如果已经能到达最后一个位置
        if max_reach >= n - 1:
            return True
    
    return True

# 测试
nums1 = [2, 3, 1, 1, 4]
nums2 = [3, 2, 1, 0, 4]
print(can_jump(nums1))  # True
print(can_jump(nums2))  # False

题目5:跳跃游戏 II(LeetCode 45)

问题:跳到最后一个位置的最少跳跃次数。

贪心策略:在当前能到达的范围内,选择能跳到最远的下一步

python

复制代码
def jump(nums):
    """
    跳跃游戏 II - 最少跳跃次数
    """
    n = len(nums)
    if n <= 1:
        return 0
    
    jumps = 0  # 跳跃次数
    current_end = 0  # 当前跳跃能到达的最远位置
    max_reach = 0  # 下一次跳跃能到达的最远位置
    
    for i in range(n - 1):
        # 更新下一次能到达的最远位置
        max_reach = max(max_reach, i + nums[i])
        
        # 如果到达了当前跳跃的边界
        if i == current_end:
            jumps += 1
            current_end = max_reach
            
            # 如果已经能到终点
            if current_end >= n - 1:
                break
    
    return jumps

# 测试
nums = [2, 3, 1, 1, 4]
print(f"最少需要 {jump(nums)} 次跳跃")  # 输出: 2

题目6:划分字母区间(LeetCode 763)

问题:将字符串划分为尽可能多的片段,使得同一个字母最多出现在一个片段中。

贪心策略:记录每个字母最后出现的位置,在区间内扩展右边界

python

复制代码
def partition_labels(s):
    """
    划分字母区间
    返回:每个片段的长度列表
    """
    # 记录每个字符最后出现的位置
    last_pos = {}
    for i, char in enumerate(s):
        last_pos[char] = i
    
    result = []
    start = 0
    end = 0
    
    for i, char in enumerate(s):
        # 更新当前片段的结束位置
        end = max(end, last_pos[char])
        
        # 如果当前位置就是结束位置
        if i == end:
            result.append(end - start + 1)
            start = end + 1
    
    return result

# 测试
s = "ababcbacadefegdehijhklij"
print(f"分区长度: {partition_labels(s)}")  # [9, 7, 8]

题目7:加油站(LeetCode 134)

问题:环形路上有 n 个加油站,第 i 个加油站有 gasi 升汽油,从 i 到 i+1 需要 costi 升汽油。从哪个加油站出发可以走完全程?

贪心策略:如果总油量 >= 总消耗,一定有解;从油量赤字之后的下一个位置开始

python

复制代码
def can_complete_circuit(gas, cost):
    """
    加油站问题
    返回:起点索引,如果无法走完返回 -1
    """
    total_gas = 0
    total_cost = 0
    current_gas = 0
    start = 0
    
    for i in range(len(gas)):
        total_gas += gas[i]
        total_cost += cost[i]
        current_gas += gas[i] - cost[i]
        
        # 如果当前油量不够,说明从 start 到 i 这段都不行
        if current_gas < 0:
            start = i + 1
            current_gas = 0
    
    # 如果总油量 >= 总消耗,一定有解
    return start if total_gas >= total_cost else -1

# 测试
gas = [1, 2, 3, 4, 5]
cost = [3, 4, 5, 1, 2]
print(f"从第 {can_complete_circuit(gas, cost)} 个加油站出发")

三、贪心算法使用建议

✅ 什么时候用贪心?

  • 问题具有贪心选择性质

  • 问题的最优解包含子问题的最优解

  • 经典问题:找零钱、活动选择、哈夫曼编码、最小生成树、最短路径(Dijkstra)

❌ 什么时候不用贪心?

  • 需要回溯才能找到最优解(如背包问题的 0-1 版本)

  • 当前选择会影响未来的选择(如旅行商问题)

  • 贪心能得到局部最优但不能保证全局最优

四、常见题型总结

类型 经典题目 贪心策略
区间问题 活动选择、无重叠区间 按右端点排序
分配问题 分发饼干、分发糖果 排序后从最小开始匹配
跳跃问题 跳跃游戏 I/II 维护能到达的最远位置
字符串处理 划分字母区间、移除数字 贪心选择边界
数组处理 加油站、股票买卖 累加差值找转折点

五、时间复杂度总结

  • 排序类(分发饼干、活动选择):O(n log n)

  • 遍历类(跳跃游戏、加油站):O(n)

  • 空间复杂度:通常 O(1) 或 O(n)

相关推荐
-dzk-19 小时前
【贪心算法】LC 121.买卖股票的最佳时机
算法·贪心算法
weixin_BYSJ19876 天前
springboot酒店管理系统--附源码27436
java·spring boot·python·随机森林·贪心算法·eclipse·django
旖旎夜光6 天前
LeetCode 397:整数替换(贪心问题) —— 题解
数据结构·c++·算法·leetcode·贪心算法
小星星闪亮登场7 天前
Codeforces Round 1115 ( Div. 2)
数据结构·c++·算法·贪心算法·排序算法·codeforces
oier_Asad.Chen7 天前
【洛谷题解/AcWing题解/真题】洛谷P2330【SCOI2005】繁忙的都市(Kruskal算法的再探究))
c++·算法·贪心算法·图论·最小生成树·kruskal
AAA@峥7 天前
K8s 资源混乱怎么办?Namespace 隔离 + 上下文切换实战
容器·贪心算法·kubernetes
Tongzhi20267 天前
从部署到运维:通芝科技无感考勤一体机的全流程效率解析
运维·数据结构·科技·算法·贪心算法
旖旎夜光9 天前
LeetCode 991: 坏了的计算器(贪心算法) —— 题解
数据结构·c++·算法·leetcode·贪心算法
旖旎夜光9 天前
LeetCode 553:最优除法(贪心算法) —— 题解
数据结构·c++·算法·leetcode·贪心算法