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

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

文章目录

🔗 查看完整专栏(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
相关推荐
NashSKY1 小时前
波束成形MVDR (最小方差无失真响应) 算法数学原理解析
算法·矩阵
constCpp1 小时前
AI 时代的技术新人该怎么成长?
人工智能
波动几何1 小时前
医药行业文档知识参考库技能pharma-doc-reference
人工智能
XD7429716361 小时前
科技早报晚报|2026年5月17日:调度基础设施、自托管邮件引擎与 AI 仪表盘代码,今晚更值得跟进的 3 个技术机会
人工智能·科技·科技新闻·开发者工具·自托管
Lyon198505281 小时前
【握剑之手】——《文字定律》随笔
大数据·人工智能·ai写作
程序员果子1 小时前
LangGraph :构建复杂有状态智能体的核心框架
人工智能·python·架构·langchain·prompt·ai编程·langgraph
人道领域1 小时前
【LeetCode刷题日记】513.二叉树左下角值的三种解法:从常规BFS到DFS的优雅之旅
数据结构·算法·leetcode·深度优先·广度优先
大得3691 小时前
langchain使用
java·python·langchain
初心未改HD1 小时前
深度学习之优化器详解
人工智能·深度学习