Leetcode 贪心算法 1

题号直达: 121 买卖股票的最佳时机 | 55 跳跃游戏 | 45 跳跃游戏 II | 763 划分字母区间

LeetCode 121. 买卖股票的最佳时机 ↑

题目

给定数组 prices,其中 prices[i] 表示第 i 天的股价。你只能选择某一天买入并在之后某一天卖出,计算可获得的最大利润;若无法获利,返回 0。

测试样例

  • 示例 1: prices = [7,1,5,3,6,4]
    输出:5
  • 示例 2: prices = [7,6,4,3,1]
    输出:0

提示

  • 1 <= len(prices) <= 10^5
  • 0 <= prices[i] <= 10^4

代码(Python):贪心

扫描数组时维护两件事:

  • 到当前为止的最低买入价 min_val;
  • 到当前为止的最大利润 max_ans。

每到新价格,要么更新最低价,要么尝试用“当前价 - 最低价”刷新最大利润。

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        max_ans = 0
        min_val = 10 ** 4 + 1
        for i in range(len(prices)):
            if prices[i] < min_val:
                min_val = prices[i]
            else:
                if prices[i] - min_val > max_ans:
                    max_ans = prices[i] - min_val
        return max_ans

思路解析

  • 贪心点:在每一天“就地”维护历史最低价,确保卖出时利润候选始终最优。
  • 单次遍历:不需要双重循环枚举买卖日。
  • 复杂度:时间 O(n),额外空间 O(1)。

LeetCode 55. 跳跃游戏 ↑

题目

给定非负整数数组 nums,你初始位于下标 0。nums[i] 表示在下标 i 最多可以向前跳的步数。判断是否能够到达最后一个下标。

测试样例

  • 示例 1: nums = [2,3,1,1,4]
    输出:true
  • 示例 2: nums = [3,2,1,0,4]
    输出:false

提示

  • 1 <= len(nums) <= 10^4
  • 0 <= nums[i] <= 10^5

代码(Python):贪心(从后往前判可达,唯一解法)

维护变量 gap,表示“当前下标要想跳到已知可达终点,至少还需要的步数”。从后往前扫:

  • 若 nums[i] >= gap,则 i 本身也可达终点,gap 重置为 1;
  • 否则 gap += 1,表示还要多跨过一个位置。

最终若 gap == 1,说明起点可达终点。

class Solution(object):
    def canJump(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        length = len(nums)
        index = length - 1
        gap = 1
        # 从后往前遍历,只要前一个不为0就可达
        for i in range(length - 2, -1, -1):
            if nums[i] >= gap:
                index = i        # i号可达终点
                gap = 1
            else:
                gap += 1

        if gap == 1:
            return True
        else:
            return False

思路解析

  • 逆向贪心:把“能到终点”的条件向左传播,逐步判断更左侧位置是否也能接上这条可达链。
  • gap 含义:当前点至少要跳 gap 步才可连接到“已知可达终点”的最近位置。
  • 复杂度:单次逆序扫描,时间 O(n),额外空间 O(1)。

LeetCode 45. 跳跃游戏 II ↑

题目

给定长度为 n 的数组 nums,你初始位于下标 0,nums[i] 表示从下标 i 最多可跳的长度。返回到达 n-1 的最小跳跃次数(题目保证可达)。

测试样例

  • 示例 1: nums = [2,3,1,1,4]
    输出:2
  • 示例 2: nums = [2,3,0,1,4]
    输出:2

提示

  • 1 <= len(nums) <= 10^4
  • 0 <= nums[i] <= 1000
  • 题目保证可以到达 n - 1

代码(Python)方法1:逆序 DP

从后往前计算 record[i]:表示从 i 跳到终点的最小步数。

  • 若 nums[i] == 0,则该点无法前进,记为一个很大值;
  • 否则可跳到区间 (i+1 .. i+nums[i]),取该区间最小 record 再 +1。
class Solution(object):
    def jump(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        record = [0] * len(nums)
        for i in range(len(nums) - 2, -1, -1):
            if nums[i] == 0:
                record[i] = 10 ** 4 + 1
            else:
                max_idx = min(len(nums) - 1, i + nums[i]) + 1
                record[i] = min(record[(i + 1) : max_idx]) + 1
        return record[0]

思路解析(方法1)

  • 状态定义:record[i] 是“从 i 到终点的最少跳数”。
  • 转移:record[i] = 1 + min(record[i+1 .. i+nums[i]])(注意右边界不越界)。
  • 边界:终点 record[n-1] = 0,从后向前可直接复用已算状态。
  • 复杂度:该写法每个位置可能扫描一段区间,最坏时间 O(n^2),空间 O(n);可在后续方法中优化到 O(n) 贪心。

代码(Python)方法2:贪心分层(线性)

跳跃游戏 II 贪心分层扩展图解

把每次跳跃可达区间看作一层:

  • end 表示当前这一步所覆盖区间的右边界;
  • maxPos 表示在当前区间内继续往后跳,下一步最远能到哪里。

遍历到 i == end 时,说明当前层扫描完,需要“迈出一步”,于是把 end 更新为 maxPos,step += 1。

class Solution(object):
    def jump(self, nums):
        n = len(nums)
        maxPos, end, step = 0, 0, 0
        for i in range(n - 1):
            if maxPos >= i:
                maxPos = max(maxPos, i + nums[i])
                if i == end:
                    end = maxPos
                    step += 1
        return step

思路解析(方法2)

  • 核心贪心:在“当前可达区间”内尽量更新下一层最远边界 maxPos,等区间扫描完再统一增加一步。
  • 为何最优:每一步都把下一步可达范围扩到最大,相当于 BFS 的按层最短路,首次覆盖终点即最少步数。
  • 复杂度:只需一次遍历,时间 O(n),额外空间 O(1)。

LeetCode 763. 划分字母区间 ↑

题目

给定字符串 s,把它尽可能多地切成若干段,使得同一种字母只出现在同一段中。各段按原顺序拼回必须等于 s。返回每一段的长度组成的列表。

测试样例

  • 示例 1: s = "ababcbacadefegdehijhklij"
    输出:[9, 7, 8]
  • 示例 2: s = "eccbbbbdec"
    输出:[10]

提示

  • 1 ≤ len(s) ≤ 500
  • s 仅含小写英文字母

代码(Python)

class Solution(object):
    def partitionLabels(self, s):
        """
        :type s: str
        :rtype: List[int]
        """
        record = {}
        ans = []
        for i in range(len(s) - 1, -1, -1):
            if s[i] not in record:
                record[s[i]] = i
        end = 0
        start = 0
        num = 0
        for i in range(len(s)):
            end = max(record[s[start]], end)
            if start == end:
                ans.append(num + 1)
                num = -1
                start = start + 1
            else:
                start = start + 1
            num = num + 1
            if start > len(s) - 1:
                break
        return ans

思路解析(如图示贪心)

  1. 预处理:从右往左扫一遍,记录每个字符在 s 中的最后出现下标存进 record。同一字母若要只在一段里出现,这段必须覆盖该字母的首尾之间的整个区间,因此右边界至少要扩到该字母的最后位置。
  2. 贪心扩边界:左到右扫描时用 start 作为当前考察位置;每一段维护当前段的右边界 end,并不断更新
    end = max(end, record[s[start]]),表示:当前段里已经出现过的字母,其最后出现位置都不能落在段外。
  3. 切分:当 start == end 时,说明从本段起点到 end 之间,所有出现过的字母的最后出现都不超过 end,可以安全切段;段长为当前累计的 num + 1,然后重置计数并开始下一段。
  4. 为何段数最多:每一段都在满足约束的前提下尽量早结束(右边界取到刚好包住当前已出现字母的最后出现),从而贪心地最大化段数。
  • 复杂度:预处理 O(n),主扫描 O(n),哈希表 O(1) 字符集(小写字母),总体 O(n) 时间与 O(1) 额外空间(相对 n 的线性来自 ans 与 record)。
posted @ 2026-10-12 00:28  Phier  阅读(2)  评论(0)    收藏  举报