121.买卖股票的最佳时机
🎭 题目小剧场
想象你在股市观察一支股票的价格波动,想找到买入和卖出的最佳时机。你不能回到过去,只能在价格走势中找到那个"低买高卖"的黄金组合,赚取最大利润!
💡 解题思路
一次遍历解法:
维护一个变量记录当前遇到的最小价格
遍历价格数组,计算当前价格与最小价格的差值
更新最大利润
更新最小价格
🐍 Python代码实现
class Solution: def maxProfit(self, prices: List[int]) -> int: """ 一次遍历解法 - 时间复杂度O(n),空间复杂度O(1) """ min_price = float('inf') # 当前最小价格 max_profit = 0 # 最大利润 for price in prices: # 更新最小价格 if price < min_price: min_price = price # 计算当前利润 current_profit = price - min_price # 更新最大利润 if current_profit > max_profit: max_profit = current_profit return max_profit
📊 复杂度分析
时间复杂度:O(n) - 只需要遍历一次价格数组
空间复杂度:O(1) - 只使用常数额外空间
🌟 算法精髓
这个算法的巧妙之处在于"前瞻后顾"。在遍历每个价格时,我们既回顾了历史最低价,又展望了当前可能的利润,实现了动态的最优决策。
🎯 小白友好贴士
min_price = float('inf') 初始化为无穷大
max_profit = 0 初始化最大利润为0
if price < min_price 更新最小价格
current_profit = price - min_price 计算当前利润
if current_profit > max_profit 更新最大利润
🔍 算法执行过程示例
prices = [7, 1, 5, 3, 6, 4]初始:min_price = inf, max_profit = 0第1天:price = 7 min_price = min(inf, 7) = 7 current_profit = 7 - 7 = 0 max_profit = max(0, 0) = 0第2天:price = 1 min_price = min(7, 1) = 1 current_profit = 1 - 1 = 0 max_profit = max(0, 0) = 0第3天:price = 5 min_price = min(1, 5) = 1 current_profit = 5 - 1 = 4 max_profit = max(0, 4) = 4第4天:price = 3 min_price = min(1, 3) = 1 current_profit = 3 - 1 = 2 max_profit = max(4, 2) = 4第5天:price = 6 min_price = min(1, 6) = 1 current_profit = 6 - 1 = 5 max_profit = max(4, 5) = 5第6天:price = 4 min_price = min(1, 4) = 1 current_profit = 4 - 1 = 3 max_profit = max(5, 3) = 5结束:return max_profit = 5 ✅
🎯 核心思想总结
历史最低:记录遍历过程中的最低价格
即时计算:在每个价格点计算可能的利润
动态更新:不断更新最大利润
一次遍历:高效的线性时间解决方案
55.跳跃游戏
🎭 题目小剧场
想象你在玩一个跳格子游戏,每个格子上写着你能跳的最远距离。你需要判断是否能从起点跳到终点。就像超级玛丽一样,每个蘑菇都能给你不同的跳跃力!
💡 解题思路
贪心解法(反向):
从终点开始,反向思考
维护一个"可到达"的位置
检查每个位置是否能跳到"可到达"位置
如果能,更新"可到达"位置为当前位置
最后检查起点是否"可到达"
🐍 Python代码实现
class Solution: def canJump(self, nums: List[int]) -> bool: """ 贪心解法(反向) - 时间复杂度O(n),空间复杂度O(1) """ if not nums: return False # 从终点开始,维护一个可到达的位置 reachable = len(nums) - 1 # 从倒数第二个位置开始向前遍历 for i in range(len(nums) - 2, -1, -1): # 如果当前位置能跳到可到达位置 if i + nums[i] >= reachable: reachable = i # 检查起点是否可到达终点 return reachable == 0
📊 复杂度分析
时间复杂度:O(n) - 只需要遍历一次数组
空间复杂度:O(1) - 只使用常数额外空间
🌟 算法精髓
这个算法的巧妙之处在于"逆向思维"。我们不是从起点看能到哪里,而是从终点看能从哪里来,大大简化了问题。
🎯 小白友好贴士
reachable = len(nums) - 1 初始化可到达位置为终点
range(len(nums) - 2, -1, -1) 从倒数第二个位置向前遍历
i + nums[i] >= reachable 判断是否能跳到可到达位置
reachable = i 更新可到达位置
return reachable == 0 检查起点是否可到达
🔍 算法执行过程示例
示例1:nums = [2, 3, 1, 1, 4]初始:reachable = 4 (终点)第1步:i = 3, nums[3] = 1 检查:3 + 1 = 4 >= reachable = 4 ✓ 更新:reachable = 3第2步:i = 2, nums[2] = 1 检查:2 + 1 = 3 >= reachable = 3 ✓ 更新:reachable = 2第3步:i = 1, nums[1] = 3 检查:1 + 3 = 4 >= reachable = 2 ✓ 更新:reachable = 1第4步:i = 0, nums[0] = 2 检查:0 + 2 = 2 >= reachable = 1 ✓ 更新:reachable = 0 结束:reachable = 0 == 0 ✓ return True ✅示例2:nums = [3, 2, 1, 0, 4]初始:reachable = 4 (终点)第1步:i = 3, nums[3] = 0 检查:3 + 0 = 3 < reachable = 4 ✗ 不更新:reachable = 4第2步:i = 2, nums[2] = 1 检查:2 + 1 = 3 < reachable = 4 ✗ 不更新:reachable = 4第3步:i = 1, nums[1] = 2 检查:1 + 2 = 3 < reachable = 4 ✗ 不更新:reachable = 4第4步:i = 0, nums[0] = 3 检查:0 + 3 = 3 < reachable = 4 ✗ 不更新:reachable = 4结束:reachable = 4 != 0 ✗ return False ✅
🎯 核心思想总结
逆向思维:从终点向前思考
贪心策略:每次都寻找最远的可到达位置
动态更新:不断更新可到达的位置
最终判断:检查起点是否可到达终点
45.跳跃游戏 II
🎭 题目小剧场
想象你在玩一个跳格子游戏,每个格子上写着你能跳的最远距离。这次不仅要判断能否到达终点,还要找到最少的跳跃次数!就像规划旅行路线,想用最少的换乘次数到达目的地!
💡 解题思路
贪心解法(分层遍历):
维护当前层的最远位置和下一层的最远位置
遍历数组,在当前层内寻找下一层的最远位置
到达当前层边界时,跳跃次数加1,切换到下一层
继续直到到达终点
🐍 Python代码实现
class Solution: def jump(self, nums: List[int]) -> int: """ 贪心解法(分层遍历) - 时间复杂度O(n),空间复杂度O(1) """ if len(nums) <= 1: return 0 jumps = 0 # 跳跃次数 current_end = 0 # 当前层的最远位置 farthest = 0 # 下一层的最远位置 # 注意:不需要遍历最后一个元素 for i in range(len(nums) - 1): # 更新下一层的最远位置 farthest = max(farthest, i + nums[i]) # 到达当前层边界,需要跳跃 if i == current_end: jumps += 1 current_end = farthest # 如果已经能到达终点,提前结束 if current_end >= len(nums) - 1: break return jumps
📊 复杂度分析
时间复杂度:O(n) - 只需要遍历一次数组
空间复杂度:O(1) - 只使用常数额外空间
🌟 算法精髓
这个算法的巧妙之处在于"分层思考"。我们将跳跃过程看作一层层的扩展,每次在当前层内找到最优的下一层起点,实现最少的跳跃次数。
🎯 小白友好贴士
🔍 算法执行过程示例
示例1:nums = [2, 3, 1, 1, 4]初始:jumps = 0, current_end = 0, farthest = 0第1步:i = 0, nums[0] = 2 farthest = max(0, 0 + 2) = 2 i == current_end (0 == 0) ✓ jumps = 1, current_end = 2第2步:i = 1, nums[1] = 3 farthest = max(2, 1 + 3) = 4 i != current_end (1 != 2) ✗第3步:i = 2, nums[2] = 1 farthest = max(4, 2 + 1) = 4 i == current_end (2 == 2) ✓ jumps = 2, current_end = 4 current_end >= len(nums) - 1 (4 >= 4) ✓ break结束:return jumps = 2 ✅示例2:nums = [2, 3, 0, 1, 4]初始:jumps = 0, current_end = 0, farthest = 0第1步:i = 0, nums[0] = 2 farthest = max(0, 0 + 2) = 2 i == current_end (0 == 0) ✓ jumps = 1, current_end = 2第2步:i = 1, nums[1] = 3 farthest = max(2, 1 + 3) = 4 i != current_end (1 != 2) ✗第3步:i = 2, nums[2] = 0 farthest = max(4, 2 + 0) = 4 i == current_end (2 == 2) ✓ jumps = 2, current_end = 4 current_end >= len(nums) - 1 (4 >= 4) ✓ break结束:return jumps = 2 ✅
🎯 核心思想总结
分层遍历:将跳跃过程看作层层扩展
贪心选择:在当前层内寻找最优的下一层起点
边界判断:到达当前层边界时必须跳跃
提前终止:一旦能到达终点就立即结束
763.划分字母区间
🎭 题目小剧场
想象你在给一串字母分组,每个字母都有自己的"势力范围"(从第一次出现到最后一次出现)。你需要将这些势力范围合并,形成互不重叠的分组。就像给不同部门分配办公室,每个部门的所有员工都要在同一个办公室里!
💡 解题思路
贪心解法:
记录每个字母的最后出现位置
遍历字符串,维护当前区间的结束位置
遇到字母的最后位置超出当前区间,扩展区间
到达当前区间结束时,记录区间并开始新区间
🐍 Python代码实现
class Solution: def partitionLabels(self, s: str) -> List[int]: """ 贪心解法 - 时间复杂度O(n),空间复杂度O(1) """ # 记录每个字母的最后出现位置 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 = i + 1 return result
📊 复杂度分析
🌟 算法精髓
这个算法的巧妙之处在于"区间合并"。我们先确定每个字母的"势力范围",然后贪心地合并重叠的范围,形成最大的不重叠区间。
🎯 小白友好贴士
last_pos = {} 记录每个字母的最后位置
last_pos[char] = i 更新字母的最后位置
end = max(end, last_pos[char]) 扩展当前区间
if i == end 到达区间结束位置
result.append(end - start + 1) 记录区间长度
🔍 算法执行过程示例
示例1:s = "ababcbacadefegdehijhklij"第1步:记录每个字母的最后位置 a: 8, b: 5, c: 7, d: 14, e: 15, f: 11, g: 13, h: 19, i: 22, j: 23, k: 20, l: 21第2步:遍历字符串,划分区间初始:start = 0, end = 0, result = [] i=0, char='a': end = max(0, 8) = 8 i=1, char='b': end = max(8, 5) = 8 i=2, char='a': end = max(8, 8) = 8 i=3, char='b': end = max(8, 5) = 8 i=4, char='c': end = max(8, 7) = 8 i=5, char='b': end = max(8, 5) = 8 i=6, char='a': end = max(8, 8) = 8 i=7, char='c': end = max(8, 7) = 8 i=8, char='a': end = max(8, 8) = 8 i == end (8 == 8) ✓ result.append(8 - 0 + 1) = [9]start = 9 i=9, char='d': end = max(9, 14) = 14 i=10, char='e': end = max(14, 15) = 15 i=11, char='f': end = max(15, 11) = 15 i=12, char='e': end = max(15, 15) = 15 i=13, char='g': end = max(15, 13) = 15 i=14, char='d': end = max(15, 14) = 15 i=15, char='e': end = max(15, 15) = 15 i == end (15 == 15) ✓ result.append(15 - 9 + 1) = [9, 7]start = 16 i=16, char='h': end = max(16, 19) = 19 i=17, char='i': end = max(19, 22) = 22 i=18, char='j': end = max(22, 23) = 23 i=19, char='h': end = max(23, 19) = 23 i=20, char='k': end = max(23, 20) = 23 i=21, char='l': end = max(23, 21) = 23 i=22, char='i': end = max(23, 22) = 23 i=23, char='j': end = max(23, 23) = 23 i == end (23 == 23) ✓ result.append(23 - 16 + 1) = [9, 7, 8]start = 24结束:return [9, 7, 8] ✅
🎯 核心思想总结
势力范围:确定每个字母的出现范围
贪心合并:不断扩展当前区间直到无法再扩展
边界判断:到达区间边界时立即切割
最优解:形成尽可能大的不重叠区间