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^50 <= 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^40 <= 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^40 <= 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:贪心分层(线性)
把每次跳跃可达区间看作一层:
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) ≤ 500s仅含小写英文字母
代码(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
思路解析(如图示贪心)
- 预处理:从右往左扫一遍,记录每个字符在
s中的最后出现下标存进record。同一字母若要只在一段里出现,这段必须覆盖该字母的首尾之间的整个区间,因此右边界至少要扩到该字母的最后位置。 - 贪心扩边界:左到右扫描时用
start作为当前考察位置;每一段维护当前段的右边界end,并不断更新
end = max(end, record[s[start]]),表示:当前段里已经出现过的字母,其最后出现位置都不能落在段外。 - 切分:当
start == end时,说明从本段起点到end之间,所有出现过的字母的最后出现都不超过end,可以安全切段;段长为当前累计的num + 1,然后重置计数并开始下一段。 - 为何段数最多:每一段都在满足约束的前提下尽量早结束(右边界取到刚好包住当前已出现字母的最后出现),从而贪心地最大化段数。
- 复杂度:预处理 O(n),主扫描 O(n),哈希表 O(1) 字符集(小写字母),总体 O(n) 时间与 O(1) 额外空间(相对
n的线性来自ans与record)。

浙公网安备 33010602011771号