文章目录
- 1. 什么是贪心?
- 2. 分发饼干
- 3. 摆动序列
- 4. 最大子数组和
- 5. 买卖股票的最佳时机 II
- 6. 跳跃游戏
- 7. 跳跃游戏 II
- 8.K 次取反后最大化的数组和
- 9.加油站
- 10.分发糖果
- 11.柠檬水找零
1. 什么是贪心?
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?
指定每次拿最大的,最终结果就是拿走最大数额的钱。
每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
贪心算法一般分为如下四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。
2. 分发饼干
题目:
思路:
大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
代码:
class Solution:def findContentChildren(self, g, s):g.sort() # 将孩子的贪心因子排序s.sort() # 将饼干的尺寸排序index = len(s) - 1 # 饼干数组的下标,从最后一个饼干开始result = 0 # 满足孩子的数量for i in range(len(g)-1, -1, -1): # 遍历胃口,从最后一个孩子开始if index >= 0 and s[index] >= g[i]: # 遍历饼干result += 1index -= 1return result
3. 摆动序列
题目:
思路:
本题要考虑三种情况:
情况一:上下坡中有平坡
情况二:数组首尾两端
情况三:单调坡中有平坡
记录峰值的条件应该是: (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)
代码:
class Solution:def wiggleMaxLength(self, nums):if len(nums) <= 1:return len(nums) # 如果数组长度为0或1,则返回数组长度curDiff = 0 # 当前一对元素的差值preDiff = 0 # 前一对元素的差值result = 1 # 记录峰值的个数,初始为1(默认最右边的元素被视为峰值)for i in range(len(nums) - 1):curDiff = nums[i + 1] - nums[i] # 计算下一个元素与当前元素的差值# 如果遇到一个峰值if (preDiff <= 0 and curDiff > 0) or (preDiff >= 0 and curDiff < 0):result += 1 # 峰值个数加1preDiff = curDiff # 注意这里,只在摆动变化的时候更新preDiffreturn result # 返回最长摆动子序列的长度
4. 最大子数组和
题目:
思路:
采用贪心策略,如果 -2 1 在一起,计算起点的时候,一定是从 1 开始计算,因为负数只会拉低总和,这就是贪心贪的地方!
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
代码:
class Solution:def maxSubArray(self, nums):result = float('-inf') # 初始化结果为负无穷大count = 0for i in range(len(nums)):count += nums[i]if count > result: # 取区间累计的最大值(相当于不断确定最大子序终止位置)result = countif count <= 0: # 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和count = 0return result
5. 买卖股票的最佳时机 II
题目:
思路:
采用贪心策略,局部最优:收集每天的正利润,全局最优:求得最大利润。
代码:
class Solution:def maxProfit(self, prices: List[int]) -> int:result = 0num = 0for i in range(len(prices) - 1):num = prices[i + 1] - prices[i]if num > 0:result += numreturn result
6. 跳跃游戏
题目:
思路:
贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
代码:
class Solution:def canJump(self, nums: List[int]) -> bool:cover = 0if len(nums) == 1: return Truei = 0# python不支持动态修改for循环中变量,使用while循环代替while i <= cover:cover = max(i + nums[i], cover)if cover >= len(nums) - 1: return Truei += 1return False
7. 跳跃游戏 II
题目:
思路:
理解本题的关键在于:以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点,这个范围内最少步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。
代码:
class Solution:def jump(self, nums: List[int]) -> int:if len(nums) == 1:return 0result = 0max_cover = 0cur = 0while cur <= max_cover:for i in range(cur,max_cover + 1):max_cover = max(nums[i] + i,max_cover)if max_cover >= len(nums) - 1:result += 1return resultresult += 1return result
8.K 次取反后最大化的数组和
题目:
思路:
本题的解题步骤为:
第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
第二步:从前向后遍历,遇到负数将其变为正数,同时K–
第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
第四步:求和
代码:
class Solution:def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:nums.sort(key=lambda x:abs(x),reverse=True) # 按绝对值从大到小排序result = 0for i in range(len(nums)):if k > 0 and nums[i] < 0:nums[i] = -nums[i]k -= 1if k % 2 == 1:nums[-1] = -nums[-1]result = sum(nums)return result
9.加油站
题目:
思路:
首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
每个加油站的剩余量rest[i]为gas[i] - cost[i]。
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
代码:
class Solution:def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:curSum = 0 # 当前累计的剩余油量totalSum = 0 # 总剩余油量start = 0 # 起始位置for i in range(len(gas)):curSum += gas[i] - cost[i]totalSum += gas[i] - cost[i]if curSum < 0: # 当前累计剩余油量curSum小于0start = i + 1 # 起始位置更新为i+1curSum = 0 # curSum重新从0开始累计if totalSum < 0:return -1 # 总剩余油量totalSum小于0,说明无法环绕一圈return start
10.分发糖果
题目:
思路:
这道题目一定是要确定一边之后,再确定另一边。分别从前往后和从后往前遍历,需要同时满足才行,有局部最优推出全局最优。
代码:
class Solution:def candy(self, ratings: List[int]) -> int:geting = [1] * len(ratings)result = 0for i in range(1,len(ratings)): # 从左往右if ratings[i] > ratings[i - 1]:geting[i] = geting[i - 1] + 1for i in range(len(ratings)-2,-1,-1): # 从右往左if ratings[i] > ratings[i + 1]:geting[i] = max(geting[i],geting[i + 1] + 1)for i in range(len(geting)): result += geting[i]return result
11.柠檬水找零
题目:
思路:
只需要维护三种金额的数量,5,10和20。
有如下三种情况:
情况一:账单是5,直接收下。
情况二:账单是10,消耗一个5,增加一个10
情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。
代码:
class Solution:def lemonadeChange(self, bills: List[int]) -> bool:five = 0ten = 0 for bill in bills:# 情况一:收到5美元if bill == 5:five += 1# 情况二:收到10美元if bill == 10:if five <= 0:return Falseten += 1five -= 1# 情况三:收到20美元if bill == 20:# 先尝试使用10美元和5美元找零if five > 0 and ten > 0:five -= 1ten -= 1#twenty += 1# 如果无法使用10美元找零,则尝试使用三张5美元找零elif five >= 3:five -= 3else:return Falsereturn True