Hot 100(三)

337. 打家劫舍 III

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def __init__(self):
        self.memo = dict()

    def rob(self, root: Optional[TreeNode]) -> int:
        # # 暴力搜索 + 记忆化
        # if not root:
        #     return 0
        # if self.memo.get(root):
        #     return self.memo[root]
        # # 如果选择 root
        # res1 = root.val
        # if root.left:
        #     res1 += self.rob(root.left.left) + self.rob(root.left.right)
        # if root.right:
        #     res1 += self.rob(root.right.left) + self.rob(root.right.right)
        # # 如果不选择 root
        # res2 = self.rob(root.left) + self.rob(root.right)
        # res = max(res1, res2)
        # self.memo[root] = res
        # return res

        # 动态规划
        # f 表示选择节点 root 的情况下,所能获得的最大利益 f(root) = root.val + g(root.left) + g(root.right)
        # g 表示不选择节点 root 的情况下,所能获得的最大利益 g(root) = max(f(root.left), g(root.left)) + max(f(root.right), g(root.right))
        
        def DFS(root):
            if not root:
                return 0, 0
            # 后序遍历
            leftchild_steal, leftchild_nosteal = DFS(root.left)
            rightchild_steal, rightchild_nosteal = DFS(root.right)

            # 偷当前node,则最大收益为【投当前节点+不偷左右子树】
            steal = root.val + leftchild_nosteal + rightchild_nosteal
            # 不偷当前node,则可以偷左右子树
            nosteal = max(leftchild_steal, leftchild_nosteal) + max(rightchild_steal, rightchild_nosteal)
            return steal, nosteal
            
        return max(DFS(root))

338. 比特位计数

class Solution:
    def countBits(self, n: int) -> List[int]:
        # 动态规划
        # 状态转移:奇数时,dp[i] = dp[i-1] + 1
        # 偶数时,dp[i] = dp[i // 2]
        res = [0 for _ in range(n+1)]
        for i in range(1, n+1):
            if i % 2 == 1:
                res[i] = res[i-1] + 1
            else:
                res[i] = res[i//2]
        return res

394. 字符串解码

class Solution:
    def decodeString(self, s: str) -> str:
        # 辅助栈
        stack, res, multi = [], "", 0
        for c in s:
            # 左括号时,重置 multi 和 res,multi 为要重复的次数,res 为待重复的字符串
            if c == '[':
                stack.append([multi, res])
                res, multi = "", 0
            # 右括号时,更新 res
            elif c == ']':
                cur_multi, last_res = stack.pop()
                res = last_res + cur_multi * res
            elif '0' <= c <= '9':
                multi = multi * 10 + int(c)            
            else:
                res += c
        return res

399. 除法求值

406. 根据身高重建队列

class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        # 按照第一个元素降序排列,再按照第二个元素升序排列
        # 保证先放入 res 中的都是高个子
        res = []
        people = sorted(people, key = lambda x: (-x[0], x[1]))
        for p in people:
            if len(res) <= p[1]:
                res.append(p)
            else:
                res.insert(p[1], p)
        return res

416. 分割等和子集

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 动态规划
        # dp[i][j]表示 nums[:i+1]数组中是否可以选出和为 j
        n = len(nums)
        # 特殊情况处理
        if n < 2:
            return False
        numsSum = sum(nums)
        maxNum = max(nums)
        # 和为奇数,无法选择
        if numsSum % 2 == 1:
            return False
        # 最大的数大于总和的一半,无法选择
        if 2 * maxNum > numsSum:
            return False

        target = numsSum // 2
        
        # dp数组和边界条件
        dp = [[False]*(target+1) for _ in range(n)]
        for i in range(n):
            dp[i][0] = True
        dp[0][nums[0]] = True

        for i in range(1, n):
            num = nums[i]
            for j in range(1, target+1):
                if j >= num:
                    # 可以选 num 或不选
                    dp[i][j] = dp[i-1][j] or dp[i-1][j-num]
                else:
                    # 不能选 num
                    dp[i][j] = dp[i-1][j]
        
        return dp[n-1][target]

        # 空间可优化到一维,但需要注意从后往前更新,不然 dp[j-num]会被更新,不再是上一行的值

437. 路径总和 III

DFS or 前缀和

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
        # DFS
        # 定义 rootSum(p, target) 表示以 p 为起点,且路径和为 target 的路径个数
        # 时间复杂度 O(n^2)
        def rootSum(p, target):
            if not p:
                return 0
            val = p.val
            res = 0
            # 这个不能直接 return,有负数
            if val == target:
                res += 1
            res += rootSum(p.left, target-val) + rootSum(p.right, target-val)
            return res

        if not root:
            return 0
        # 以根节点为起点的个数
        res = rootSum(root, targetSum)
        # 左子树的个数(不一定左节点是根节点)
        res += self.pathSum(root.left, targetSum)
        # 右子树的个数(不一定右节点是根节点)
        res += self.pathSum(root.right, targetSum)
        return res

        # # 前缀和
        # # 保存根节点到当前节点(不包括)的前缀和,当遍历到当前节点时,根节点到当前节点(包括)和为 curr,在前缀和 map 中查找是否存在和为 curr-target 的路径,则一定存在和为 target 的路径
        # prefix = collections.defaultdict(int)
        # prefix[0] = 1  # 空路径需要保存,处理 curr=target 的路径

        # # 返回 root 为根节点的和为 target 的路径条数,curr 为根节点到当前节点(不包括)和
        # def dfs(root, curr):
        #     if not root:
        #         return 0
            
        #     ret = 0
        #     curr += root.val  # 根节点到当前节点(包括)和
        #     ret += prefix[curr - targetSum]  # 查找和为 curr-target 的路径个数
        #     prefix[curr] += 1  # 更新前缀和
        #     ret += dfs(root.left, curr)
        #     ret += dfs(root.right, curr)
        #     prefix[curr] -= 1  # 递归退出后需要删除,避免不是一条路径的

        #     return ret

        # return dfs(root, 0)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值