代码随想录算法训练营第三十七天 | 738. 单调递增的数字、714. 买卖股票的最佳时机含手续费、968. 监控二叉树、贪心算法总结篇

114 阅读4分钟

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

贪心算法总结篇

贪心理论基础

  1. 贪心很简单,就是常识?

    贪心思路往往很巧妙,并不简单

  2. 贪心有没有固定的套路?

    贪心无套路,也没有框架之类的,需要多看多练培养感觉才能想到贪心的思路。

  3. 究竟什么题目是贪心呢?

    如果找出局部最优并可以推出全局最优,就是贪心,如果局部最优都没找出来,就不是贪心,可能是单纯的模拟。

  4. 如何知道局部最优推出全局最优,有数学证明么?

    在做贪心题的过程中,如果再来一个数据证明,其实没有必要,手动模拟一下,如果找不出反例,就试试贪心。面试中,代码写出来跑过测试用例即可,或者自己能自圆其说理由就行了

两个维度权衡问题

  • 在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
  • 需要注意排序的key和排序后遍历的方向