贪心5.

738. 单调递增的数字

这题主要难点是转化问题。

经过我的缜密分析,满足条件的结果可以分为两种情况:

  1. 最高位数字-1,后面所有位都是9。 比如10=>9, 332=>299.
  2. 最高位数字不变,后面位次是符合题意的最优解,并且两部分合起来仍然是符合题意的。比如1234=>1 234,234是234的最优解,并且2>=1,所以合起来仍是最优解。

所以递归很容易就看出来了吧。

def monotoneIncreasingDigits(self, n: int) -> int:
    def maxMonotoneNumber(s):
        if len(s) == 1:
            return s
        # 情况1
        backup = str(int(s[0])-1)
        for _ in range(len(s)-1):
            backup += "9"
        # 情况2(子问题)
        sub = maxMonotoneNumber(s[1:])
        if sub[0] < s[0]:
            return backup
        return s[0]+sub
    return int(maxMonotoneNumber(str(n)))

714. 买卖股票的最佳时机含手续费

如果说122. 买卖股票的最佳时机 II是看到单调递增区间就allin,那么这题就更为复杂。反正我是没想出来贪心怎么做。

贪心思路可以这么理解:

如果使用贪心策略,就是最低值买,最高值(如果算上手续费还盈利)就卖。

此时无非就是要找到两个点,买入日期,和卖出日期。

所以我们在做收获利润操作的时候其实有三种情况:

这题看看就好,DP才是常规做法。

def maxProfit(self, prices: List[int], fee: int) -> int:
    result = 0
    minPrice = prices[0]
    for i in range(1, len(prices)):
        # 情况二
        if prices[i] < minPrice:
            minPrice = prices[i]
        # 情况三
        elif prices[i] >= minPrice and prices[i] <= minPrice + fee: 
            continue
        else: 
            # 情况一
            result += prices[i] - minPrice - fee
            minPrice = prices[i] - fee
    return result

968. 监控二叉树

贪心最后一题来个hard。

这题最直接的想法是用递归来做,用前序遍历,对于每一个经过的节点,维持一个invariant:递归遍历过之后root必须被监控到(或者root中含有摄像头)。不过就算加了memoization还是超时了。。

def minCameraCover(self, root: TreeNode) -> int:
    memo = {}
    # invariant: root should be covered after calling helper(root, T/F)
    def helper(root, covered, rootCam=False):
        if not root:
            return 0
        if (root, covered, rootCam) in memo:
            return memo[(root, covered, rootCam)]
        res = 0
        if covered:
            if rootCam: # 下面的a情况
                res = helper(root.left, True) + helper(root.right, True)
            else: #下面的c情况
                noroot = helper(root.left, False) + helper(root.right, False)
                root = 1 + helper(root.left, True) + helper(root.right, True)
                res = min(root, noroot)
        else: #下面的b情况
            left = 1 + helper(root.left, True, True)+helper(root.right, False) if root.left else inf
            right = 1 + helper(root.left, False)+helper(root.right, True, True) if root.right else inf
            root = 1 + helper(root.left, True)+helper(root.right, True)

            res = min(left, # root camera
                       right, # left camera
                       root) # right camera

        memo[(root, covered, rootCam)] = res
        return res
    return helper(root, False)

看了题解,这题要过还是用DP。实际上想法和之前我的解法很像。

为什么要设这三个状态呢?因为root要被监控到,有三种情况:1.root自己放摄像头(a)。2. root没被父亲监控,但被孩子监控(b)。3. root被父亲监控,孩子监不监控无所谓(c)。

所以

base case:如果root为空,则root上不可能放摄像头。根据语义,返回[inf, 0, 0]。

返回值:b。

不难写出如下代码:

def minCameraCover(self, root: TreeNode) -> int:
    def dfs(root: TreeNode) -> List[int]:
        if not root:
            return [float("inf"), 0, 0]

        la, lb, lc = dfs(root.left)
        ra, rb, rc = dfs(root.right)
        a = lc + rc + 1
        b = min(a, la + rb, ra + lb)
        c = min(a, lb + rb)
        return [a, b, c]

    a, b, c = dfs(root)
    return b

至于还有一种贪心解法,我只能说想不到,也无所谓了。