贪心算法基础原理与题目说明

贪心算法基础原理与题目说明

文章目录

🔗 查看完整专栏(LeetCode基础算法专栏)

特别说明:

本文为个人的 LeetCode 刷题与学习笔记,内容仅供学习与交流使用,禁止转载或用于商业用途。需要强调的是,文中的题目解法不一定是最优解(可能存在时间或空间复杂度的进一步优化空间),主要目的是分享个人的解题思路与逻辑实现,仅供参考。 笔记内容为个人理解与总结,可能存在疏漏或偏差,欢迎读者自行甄别并交流探讨。

一、 什么是贪心算法?

贪心算法(Greedy Algorithm)的核心思想是:局部最优 → \rightarrow → 全局最优。

在对问题求解时,总是做出在当前看来是最好的选择。也就是说,它不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解,并期望通过这一系列的局部最优选择,最终推导出全局最优解。

1.1 贪心算法的显著特点

  • 无后效性(不回溯):每一步的选择一旦做出,就不可更改,绝不回头重新考虑其他的可能性。
  • 注重当下:不考虑未来的长远影响,只根据当前状态做出最有利的判断。
  • 代码极简,证明极难 :贪心算法的代码往往非常短小、顺畅、直接;真正的难点在于证明该问题是否满足"贪心选择性质"(即证明局部最优真的能组合成全局最优)。

二、 股票买卖模型

121. 买卖股票的最佳时机

题目描述:

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择某一天 买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。若不能获取任何利润,返回 0。

解题思路:

卖出价固定时,买入价越低 → \rightarrow → 利润越高。

贪心策略:将遍历到的每一天都当作"潜在的卖出日"。对于这个卖出日,我们只关心它之前出现过的历史最低价格,其他较高的价格波动对我们毫无意义。

  1. 遍历每一天。

  2. 实时维护并更新到今天为止的历史最低价(最优买入价)。

  3. 计算如果今天卖出的利润(今天价格 - 历史最低价),并全程更新全局最大利润。

    (这就是贪心的本质:每一步只保留对我最有利的局部最优信息,扔掉所有无用信息)

核心代码:

py 复制代码
from typing import List

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        """
        贪心策略:把每一天作为潜在卖出日,只用这一日之前的最低买入价作为成本来计算利润,持续更新利润最大值。
        """
        min_cost = float('inf')
        max_profit = 0

        # 遍历潜在卖出日
        for price in prices:
            # 维护历史最低买入价
            min_cost = min(price, min_cost)
            # 计算当前利润,并更新全局最大利润
            max_profit = max(max_profit, price - min_cost)
        
        return max_profit

三、 跳跃游戏系列(区间覆盖问题)

跳跃游戏系列是贪心算法在"区间覆盖"问题上的经典体现。核心在于维护当前可达的最远边界。

55. 跳跃游戏

题目描述:

给你一个非负整数数组 nums ,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

解题思路:

核心贪心策略:始终维护当前可到达的最远位置(最远覆盖范围)。通过局部最优(每一步更新最远可达范围)推导全局最优(能否覆盖数组终点)。

  1. 遍历数组,只要当前位置 i 在可到达的范围 max_arrive 内,就尝试用 i + nums[i] 去更新 max_arrive。
  2. 只要 max_arrive 大于等于数组最后一个下标,说明终点可达,提前返回 True。
  3. 这种贪心选择能确保不遗漏任何可到达终点的可能性。

核心代码:

py 复制代码
from typing import List

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        """
        贪心策略:每次更新最远可以到达的位置,判断最远可达位置是否覆盖了数组终点。
        """
        max_arrive = 0
        n = len(nums)

        for i in range(n):
            # 1. 只有当前位置可达时,才能从这里继续向后跳
            if i <= max_arrive:
                # 2. 更新最远可达位置
                max_arrive = max(max_arrive, i + nums[i])
                # 3. 已经覆盖终点,提前返回
                if max_arrive >= n - 1:
                    return True
                    
        return False

45. 跳跃游戏 II

题目描述:

给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。

返回到达 n - 1 的最小跳跃次数 。测试用例保证一定可以到达 n - 1。

解题思路:

本题是跳跃游戏的进阶,求最小跳跃次数。

核心贪心策略:在当前这步能覆盖的所有起跳点中,选择那个能把"下一步的最远边界"推得更远的点。

  1. 维护两个变量:max_arrive(当前探路能找到的全局最远位置)和 cur_arrive(当前这一步跳跃所能覆盖的右边界)。
  2. 遍历数组(不包含最后一个元素),不断更新 max_arrive。
  3. 当遍历索引 i 到达了当前步的边界 cur_arrive 时,说明当前这步的潜力已经用尽,必须 进行下一次跳跃了。此时跳跃次数 +1,并将边界更新为 max_arrive。

核心代码:

py 复制代码
from typing import List

class Solution:
    def jump(self, nums: List[int]) -> int:
        """
        局部最优:单次跳跃覆盖最大距离 → 全局最优:总跳跃次数最少
        """
        max_arrive = 0   # 探路的最远边界
        cur_arrive = 0   # 当前跳跃步数能覆盖的右边界
        ans = 0          # 跳跃次数

        # 不遍历最后一个元素,因为题目保证能到达,若正好在终点起跳会多算一次
        for i in range(len(nums) - 1):
            # 探索在当前步的范围内,下一步最远能跳到哪
            max_arrive = max(max_arrive, i + nums[i])
            
            # 当遍历到当前步的极限边界时,必须进行下一次跳跃
            if i == cur_arrive:
                ans += 1
                cur_arrive = max_arrive

        return ans

四、 贪心与哈希的巧妙结合

763. 划分字母区间

题目描述:

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

解题思路:

贪心策略:寻找每个片段可能的最小结束下标。

由于同一个字母必须在一个片段中,这意味着如果当前片段包含了字符 c,那么这个片段的结束位置至少要延伸到 c 在字符串中最后一次出现的位置。

  1. 第一次遍历:用哈希表(或字典)记录每个字符最后出现的位置索引。
  2. 第二次遍历:维护当前片段的起始点 start 和不断动态延伸的结束边界 end。
  3. 每遍历到一个字符,就用它的最后出现位置去挑战并更新 end。
  4. 当遍历索引 i 刚好到达 end 时,说明当前片段内所有字符的最远位置都只到这,可以完美切割。记录长度,进入下一个片段。

核心代码:

py 复制代码
import collections
from typing import List

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        """
        贪心策略:维护当前区间内所有字符的最远出现位置,当遍历到达这个最远位置时,完成一次最小合法的完美切割。
        """
        # 1. 记录每个字符最后一次出现的位置
        endhash = collections.defaultdict(int)
        for i in range(len(s)):
            endhash[s[i]] = i
        
        start = 0
        end = 0
        ans = []

        # 2. 遍历字符串,寻找切分点
        for i in range(len(s)):
            # 动态延伸当前片段的结束边界
            end = max(end, endhash[s[i]])

            # 当索引到达当前要求的最远边界时,完成一次合法切分
            if i == end:
                ans.append(end - start + 1)
                # 更新下一个片段的起始点
                start = end + 1
        
        return ans
相关推荐
sell-cloud3 小时前
Prompt Engineering要凉了?Fable 5.5能“猜“意图,但银行正在封杀AI Agent——企业AI的真正护城河是什么
人工智能·prompt
Navicat中国4 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI5 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区5 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
XCDoris7 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy7 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V7 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI7 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技7 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
gCode Teacher 格码致知7 小时前
Pandas教学-17:Series与Dataframe的关系
开发语言·python·pandas