贪心
理论
1、什么是贪心:
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
2、什么时候用贪心:
若能通过局部最优,推出整体最优就使用贪心
3、与动态规划的区别:
从一堆钞票中拿走十张使金额最大——贪心
从一堆不同体积的物品中选若干将背包装满——动态规划
4、贪心问题解法:
想清楚 局部最优 是什么,如果推导出全局最优
题目
455.分发饼干
思路:对孩子的胃口和饼干的大小由小到大排序,优先喂小胃口的孩子
class Solution:
def findContentChildren(self, g: List[int], s: List[int]) -> int:
# 小饼干给小胃口的孩子
g.sort()
s.sort()
child = 0 # 先喂胃口最小的孩子
for j in range(len(s)): # 遍历饼干,找到尽可能小的饼干
if s[j]>=g[child]:
child += 1 # 当前child找到了适合他的饼干,继续为下一个孩子分饼干
if child==len(g): # 已经为所有的孩子找到饼干
break
return child
## 376. 摆动序列 ![在这里插入图片描述](https://img-blog.csdnimg.cn/dde362af14614c8fb94a51c156c877d2.png) 法1:动态规划
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
# dp[i][0],dp[i][1]:以nums[i]为峰/谷结尾的摆动序列的最长长度
n = len(nums)
dp = [[0]*2 for _ in range(n)]
dp[0][0] = 1
dp[0][1] = 1
for i in range(1,n):
for j in range(i):
if nums[j]<nums[i]:
dp[i][0] = max(dp[j][1]+1, dp[i][0])
elif nums[j]>nums[i]:
dp[i][1] = max(dp[j][0]+1, dp[i][1])
else:
dp[i][0] = max(dp[j][0], dp[i][0])
dp[i][1] = max(dp[j][1], dp[i][1])
return max(dp[-1][0], dp[-1][1])
注:时O(N^2)
法2:找最长摆动子序列长度等效为找求峰谷数+1
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
n = len(nums)
if n==1:
return 1
ans = 1 # 第0个点
for i in range(1,n):
if i==1:
diff = nums[i]-nums[i-1]
if diff!=0: # diff正负预示上一个是上升还是下降
ans += 1
else:
if (nums[i]>nums[i-1] and diff<=0) or (nums[i]<nums[i-1] and diff>=0):
ans += 1
diff = nums[i]-nums[i-1]
return ans
注:时O(N) 空O(1)
53. 最大子数组和
法1:贪心
遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
# 贪心
n = len(nums)
count = 0
ans = -float('inf')
for i in range(n):
count = nums[i] + count
if count>ans:
ans = count
if count<=0:
count = 0
return ans
时O(N) 空O(1)
法2:动态规划
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
dp = [0]*n
dp[0] = nums[0]
ans = dp[0]
for i in range(1,n):
dp[i] = max(dp[i-1]+nums[i], nums[i])
ans = max(ans, dp[i])
return ans
时O(N) 空O(N) 注(空间可以简化,只用变量)
122. 买卖股票的最佳时机 II (可多次买卖)
法1:贪心 若第i天大于前一天(i-1)的价格,则在前一天买入 ,第i天卖出(局部最优)——所有局部最优的和(全局最优)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
profit = 0
for i in range(1,n):
if prices[i]>prices[i-1]:
profit += (prices[i]-prices[i-1])
return profit
时:O(N) 空O(1)
法2:动态规划
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp = [[0]*2 for _ in range(n)]
dp[0][0] = -prices[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
return dp[-1][1]
55. 跳跃游戏
这道题目关键点在于:不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
cover = 0
i = 0
while i<=cover:
cover = max(cover,i+nums[i])
if cover>=n-1:
return True
i += 1
return False
45. 跳跃游戏 II
45. 跳跃游戏 II
要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最小步数!
这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
class Solution:
def jump(self, nums: List[int]) -> int:
n = len(nums)
curDistance = 0 # 当前步可覆盖的最远
nextDistance = 0 # 下一步可覆盖的最远
ans = 0
for i in range(n):
nextDistance = max(nextDistance, i+nums[i])
if i == curDistance: # 到达当前步数能到达的最远端
if i>=(n-1):
break
else:
ans += 1 # 还未到终点,步数加1
curDistance = nextDistance
return ans
56. 合并区间
判断区间重叠类的问题,先排序,让所有的相邻区间尽可能的重叠在一起,按左边界,或者右边界排序都可以。如果按照左边界从小到大排序,若有 intervals[i][0] <= intervals[i - 1][1] 即intervals[i]的左边界 <= intervals[i - 1]的右边界,则一定有重叠。(注意intervals[i-1][0]≤intervals[i][0]≤intervals[i][1])
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x: x[0]) # 按intervals的第0个元素对其排序
n = len(intervals)
result = [intervals[0]] # 初始化
for i in range(1, n):
if intervals[i][0]<=result[-1][1]:
result[-1] = [result[-1][0], max(result[-1][1], intervals[i][1])]
else:
result.append(intervals[i])
return result
类似的题目:
思路:
①按右端排序 右端越短,不重叠的区间数越多
②头大于等于已有的尾,说明不重叠,不重叠区间数加一,更新尾位置
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key = lambda x: x[1]) # 按右端排序 右端越短,不重叠的区间数越多
n = len(intervals)
nooverlap = 1 # 记录不重合的区间数
end = intervals[0][1] # 第一个的尾是初始的尾
for i in range(1, n):
if intervals[i][0]>=end: # 不重叠
nooverlap += 1
end = intervals[i][1] # 更新新的尾
return n-nooverlap
时O(NlogN) 空(N)——快排
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort(key = lambda x: x[0])
n = len(points)
ans = 1
for i in range(1, n):
if points[i][0]<=points[i-1][1]: #有重合
points[i][1] = min(points[i][1], points[i-1][1]) # 两尾取其短
else: # 和前一个无重合 需另射一箭
ans += 1
return ans
注意每段的头是与上一段的尾比较,而若射了箭,上一段的尾应该是两端中较短的。
406.根据身高重建队列
解法:注:像这种两个量的要先对一个进行排序
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
# 先按身高由高到矮排序
people.sort(key = lambda x: (-x[0], x[1])) # sort默认从小到大 -x[0]就是由大到小,元组指第一个同就比较第二个
n = len(people)
queue = []
for p in people:
queue.insert(p[1], p) # 在p[1]位置处插入p
return queue
1405. 最长快乐字符串
思路:要想长度最长,则每次取个数最多的添入答案; 如果前两位已经和个数最多的一样了,则取次多的添入,如果没有次多的,那就结束了,因为此时剩下的一定都和最多的一样,会出现xxx
class Solution:
def longestDiverseString(self, a: int, b: int, c: int) -> str:
ac = [["a", a], ["b", b], ["c", c]]
ans = []
flag = True
while flag:
ac.sort(key = lambda x: -x[1]) # -x降序
for i, (sym, num) in enumerate(ac): # enumerate 返回索引i及内容(sym,num)
if len(ans)>=2 and sym == ans[-1] and sym == ans[-2]: # 当前最大数量的字符和ans中最后两个相同 找次大
continue # 找次大
if ac[i][1]>0: # 当前字符还有字母没用完
ans.append(sym)
ac[i][1] -= 1 # 数量减1
break
else: # 字母全用完 或仅有最多的剩下 已找到最长快乐数 退出
flag = False
break
return "".join(ans)
# 时O((a+b+c)ClogC) 其中C是字符种类数 由于每添一个字符就要重新对ac排序
# 空O(C) 存字符及次数