738. 单调递增的数字
贪心
- 局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]--,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
- 全局最优:得到小于等于N的最大单调递增的整数。
- 从前后向遍历会改变已经遍历过的结果, 所以从后向前遍历
class Solution:
def monotoneIncreasingDigits(self, n: int) -> int:
list_digit = list(str(n))
for i in range(len(list_digit)-1, 0, -1):
# decreasing
if int(list_digit[i]) < int(list_digit[i-1]):
list_digit[i-1] = str(int(list_digit[i-1]) - 1)
list_digit[i:] = '9' * (len(list_digit) - i)
return int("".join(list_digit))
714. 买卖股票的最佳时机含手续费
动态规划
- hold代表持有股票后剩余的钱
- cash代表卖出股票后手上剩余的钱
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
cash, hold = 0, -prices[0]
for i in range(1, len(prices)):
cash = max(cash, prices[i] + hold - fee)
hold = max(hold, cash - prices[i])
return cash
贪心
-
使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。
-
买入日期:其实很好想,遇到更低点就记录一下。
-
卖出日期:这个就不好算了,但也没有必要算出准确的卖出日期,只要当前价格大于(最低价格+手续费),就可以收获利润,至于准确的卖出日期,就是连续收获利润区间里的最后一天(并不需要计算是具体哪一天)。
-
所以我们在做收获利润操作的时候其实有三种情况:
- 情况一:收获利润的这一天并不是收获利润区间里的最后一天(不是真正的卖出,相当于持有股票),所以后面要继续收获利润。
- 情况二:前一天是收获利润区间里的最后一天(相当于真正的卖出了),今天要重新记录最小价格了。
- 情况三:不作操作,保持原有状态(买入,卖出,不买不卖)
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
min_price = prices[0]
result = 0
for i in range(1, len(prices)):
# 前一天是收获利润区间里的最后一天
if prices[i] < min_price:
min_price = prices[i]
# 收获利润的这一天并不是收获利润区间里的最后一天
if prices[i] > min_price + fee:
result += prices[i] - min_price - fee
min_price = prices[i] - fee
return result
968. 监控二叉树
贪心算法
- 使用后序遍历
- If a node has children that are not covered by a camera, then we must place a camera here. Additionally, if a node has no parent and it is not covered, we must place a camera here.
class Solution:
def minCameraCover(self, root: Optional[TreeNode]) -> int:
self.res = 0
covered = {None}
def post_order(node, parent=None):
if node:
post_order(node.left, node)
post_order(node.right, node)
# root logic
# if node is the root node and not covered or node.left/node.right not covered, we put the camera at this node
if (not parent and (node not in covered)) or (node.left not in covered or node.right not in covered):
self.res += 1
covered.update({node, parent, node.left, node.right})
post_order(root)
return self.res
贪心(使用状态标注node)
- 0: 该节点未覆盖
- 1: 该节点有摄像头
- 2: 该节点有覆盖
- 我们要从下往上看,局部最优:让叶子节点的父节点安摄像头,所用摄像头最少,整体最优:全部摄像头数量所用最少!
class Solution:
def minCameraCover(self, root: Optional[TreeNode]) -> int:
self.res = 0
def post_order(node):
# empty node is in covered state
if not node:
return 2
left = post_order(node.left)
right = post_order(node.right)
# root logic
# Case 1:
# left and right children are both covered
# node is not covered
if left == 2 and right == 2:
return 0
# Case 2:
# node.left/node.right not covered
# we need add camera here
elif left == 0 or right == 0:
self.res += 1
return 1
# Case 3:
# node.left/node.right has camera
# node is covered
elif left == 1 or right == 1:
return 2
# if root is not covered, add a camera here
if post_order(root) == 0:
self.res += 1
return self.res
贪心算法总结篇
贪心理论基础
-
贪心很简单,就是常识?
贪心思路往往很巧妙,并不简单
-
贪心有没有固定的套路?
贪心无套路,也没有框架之类的,需要多看多练培养感觉才能想到贪心的思路。
-
究竟什么题目是贪心呢?
如果找出局部最优并可以推出全局最优,就是贪心,如果局部最优都没找出来,就不是贪心,可能是单纯的模拟。
-
如何知道局部最优推出全局最优,有数学证明么?
在做贪心题的过程中,如果再来一个数据证明,其实没有必要,手动模拟一下,如果找不出反例,就试试贪心。面试中,代码写出来跑过测试用例即可,或者自己能自圆其说理由就行了
两个维度权衡问题
- 在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
- 需要注意排序的key和排序后遍历的方向