5.9 力扣 监控二叉树(从下往上标记状态) 分配硬币 图的DFS 二叉搜索树困难

968 监控二叉树
在这里插入图片描述
在这里插入图片描述
最小支配集问题
在这里插入图片描述
叶子结点位于树的边界,不会安装摄像头
有孩子结点是叶结点的结点必须安摄像头

# 图论里面的最小支配集问题,只有在二叉树这种特殊的图下 才能够求解,两种方法(贪心or动态规划)
class Solution(object):
    def minCameraCover(self, root):
        def solve(node):
            if not node: 
                return 0, 0, float('inf')
            L = solve(node.left)
            R = solve(node.right)
            '''
            条件0:当前节点的子节点都被覆盖,但不包括当前节点
            条件1:当前节点及所有子节点都被覆盖,但是当前节点位置无摄像头
            条件2:当前节点位置有摄像头

            解释一下 dp数组
            dp0代表当前节点未被覆盖,但该节点的子节点都被覆盖 时所需要的最小摄像头的数量
            dp1代表当前节点和当前节点的子节点都被覆盖,但当前节点没有摄像头,则子节点中至少有一个位于状态2,所以可以把当前的点也覆盖了  时所需要的最小摄像头的数量
            dp2代表当前节点有摄像头,则子节点可以处于任意状态  需要的最小摄像头的数量
            '''
            dp0 = L[1] + R[1] # 若想让此节点不被覆盖 但是子节点都被覆盖,则左右两个节点都要满足条件1,此节点等于左右节点dp1之和
            dp1 = min(L[2] + min(R[1:]), R[2] + min(L[1:])) # 若想让当前节点满足条件1 有两种情况:1.左节点覆盖住了此节点,此时右节点要想满足全覆盖也有两种情况(dp1 and dp2,取最小值即可)2. 右节点覆盖住了此节点,此时左节点要想满足全覆盖也有两种情况(dp1 and dp2,取最小值即可)
            dp2 = 1 + min(L) + min(R) # 若想让当前节点满足条件2,此时左右节点都被此节点覆盖,所以左右节点可以选取任意一种状态

            # print(dp0, dp1, dp2)
            return dp0, dp1, dp2

        return min(solve(root)[1:])

好理解
后序遍历 从下往上标记状态

class Solution:
    def minCameraCover(self, root: TreeNode) -> int:
    #0: 该点和子树均被覆盖,且该节点安装了监控器
    #1:该结点周围有监控器(即该节点和该节点的子节点都被监控到)或该结点不存在,但该点无监控器,都不用安装监控器
    #2:该结点附近没有监控器,该节点未被监控到但是所有子节点都被监控到,表示其附近需要监控器
        #从下往上标记节点状态
        self.ans=0
        def dfs(node):
            if not node:
                return 1
            l=dfs(node.left)
            r=dfs(node.right)
            #子结点只要有一个未被监控,则该节点放监控
            if l==2 or r==2:
                self.ans+=1
                return 0
            #孩子节点只要有一个放了监控,该节点就可以被监控到
            if l==0 or r==0:
                return 1
            return 2
        if not root:
            return 0
        #若根节点未被监控,需要放监控器
        if dfs(root)==2:
            self.ans+=1
        return self.ans

979 在二叉树中分配金币:
在这里插入图片描述
在这里插入图片描述

dfs(node):为该节点的金币过载量
对于一个叶子节点,需要移动到它中或需要从它移动到它的父亲中的金币数量为 过载量 = Math.abs(num_coins - 1)
我们可以计算出这个节点与它的子节点之间需要移动金币的数量为 abs(dfs(node.left)) + abs(dfs(node.right))(子节点移动给他或者他分配给子节点)
这个节点金币的过载量为 node.val + dfs(node.left) + dfs(node.right) - 1。

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def distributeCoins(self, root: TreeNode) -> int:
        self.ans=0
        def dfs(node):
            if not node:
                return 0
            l=dfs(node.left)
            r=dfs(node.right)
            #移动的次数等于左右子树过载量绝对值的和
            self.ans+=abs(l)+abs(r)
            #每个节点的过载量
            return node.val+l+r-1
        dfs(root)
        return self.ans

1104 二叉树寻路
在这里插入图片描述
在这里插入图片描述
完美二叉树
如果是正常从左到右,从上到下构造,则下一层的节点是当前层的2倍。
数学解法

class Solution:
    def pathInZigZagTree(self, label: int) -> List[int]:
        if label==0:
            return []
        if label==1:
            return [1]
        res=[label]
        while label>1:
            #当前层
            level=int(math.log2(label))
            #当前层起始数字
            level_start=2**level
            #上一行用于连接label的数字与上一行起始数字的偏移量
            remain=(label-level_start)//2
            # 迭代label,上一层对应路径的节点
            label=level_start-remain-1
            res.append(label)
        return res[::-1]

1315. 祖父节点值为偶数的节点和
在这里插入图片描述

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def sumEvenGrandparent(self, root: TreeNode) -> int:
        if not root:
            return 0
        self.ans=0
        def dfs(grandparent,parent,node):
            if not node:
                return 0
            if grandparent.val%2==0:
                self.ans+=node.val
            dfs(parent,node,node.left)
            dfs(parent,node,node.right)
        if root.left:
            dfs(root,root.left,root.left.left)
            dfs(root,root.left,root.left.right)
        if root.right:
            dfs(root,root.right,root.right.left)
            dfs(root,root.right,root.right.right)
        return self.ans

在搜索状态三元组 (grandparent, parent, node) 中,grandparent 和 parent 这两项我们只使用了它的值,而不使用节点本身,因此我们可以在搜索状态中用值来替换这些节点。
我们可以假设根节点有一个虚拟的祖父节点和父节点,它们的值都为 1。在搜索时,我们使用三元组 (gp_val, p_val, node) 表示搜索状态,其中 gp_val 和 p_val 分别表示祖父节点和父节点的值,node 表示当前节点。这样以来,我们就可以直接从状态 (1, 1, root) 开始直接对根节点进行搜索了。

class Solution:
    def sumEvenGrandparent(self, root: TreeNode) -> int:
        if not root:
            return 0
        self.ans=0
        def dfs(grandparent_val,parent_val,node):
            if not node:
                return 0
            if grandparent_val%2==0:
                self.ans+=node.val
            dfs(parent_val,node.val,node.left)
            dfs(parent_val,node.val,node.right)
        dfs(1,1,root)
        return self.ans

广度优先搜索:

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def sumEvenGrandparent(self, root: TreeNode) -> int:
        if not root:
            return 0
        ans=0
        stack=deque()
        stack.append(root)
        while stack:
            node=stack.popleft()
            if node.val%2==0:
                if node.left:
                    if node.left.left:
                        ans+=node.left.left.val
                    if node.left.right:
                        ans+=node.left.right.val
                if node.right:
                    if node.right.left:
                        ans+=node.right.left.val
                    if node.right.right:
                        ans+=node.right.right.val
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)
        return ans

332 重新安排行程
在这里插入图片描述
欧拉路径
每次对当前出发点的“剩余可用目的地”循环,优先去名字值小的
结束条件是层数达到完全遍历,若未达到又无可用目的地,说明这条路走不通了,进行回溯

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        queue=defaultdict(list)
        for i,j in tickets:
            queue[i].append(j)
        for i in queue:
        #邻接表排序
            queue[i].sort()
        self.ans=[]
        def dfs(v):
            while queue[v]:
            #路径检索
                dfs(queue[v].pop(0))
                #某个起点不存在路径时就会跳出while 循环,进行回溯
                #相对先找不到路径的一定是放在相对后面
               #存到最前面,最先存进来的是最后找不到路径的
            self.ans.insert(0,v)
        dfs('JFK')
        return self.ans

1302 层数最深叶子节点的和
在这里插入图片描述

# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def deepestLeavesSum(self, root: TreeNode) -> int:
        if not root:
            return 0
        res=defaultdict(list)
        stack=[(root,1)]
        level=0
        while stack:
            level+=1
            for _ in range(len(stack)):
                node,step=stack.pop(0)
                if not node.left and not node.right:
                    res[step].append(node.val)
                if node.left:
                    stack.append((node.left,step+1))
                if node.right:
                    stack.append((node.right,step+1))
        return sum(res[level])

98 验证二叉搜索树
在这里插入图片描述
二叉搜索树中序遍历是递增数列:
栈最多存储 n 个节点,因此需要额外的 O(n) 的空间

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        if not root:
            return True
        stack=[]
        pre=float('-inf')
        node=root
        while stack or node:
            while node:
                stack.append(node)
                node=node.left
            node=stack.pop()
            # 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if node.val<=pre:
                return False
            pre=node.val
            node=node.right
        return True

递归:
函数表示考虑以 root 为根的子树,判断子树中所有节点的值是否都在 (l,r)的范围内(注意是开区间)。如果 root 节点的值 val 不在 (l,r)的范围内说明不满足条件直接返回,否则我们要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。
那么根据二叉搜索树的性质,在递归调用左子树时,我们需要把上界 upper 改为 root.val,即调用 helper(root.left, lower, root.val),因为左子树里所有节点的值均小于它的根节点的值。同理递归调用右子树时,我们需要把下界 lower 改为 root.val,即调用 helper(root.right, root.val, upper)
递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,即二叉树的高度

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        if not root:
            return True
        def dfs(node,left,right):
            if not node:
                return True
            if left<node.val<right:
                return dfs(node.left,left,node.val) and dfs(node.right,node.val,right)
            return False
        return dfs(root,float('-inf'),float('inf'))

501 二叉搜索树中的众数
在这里插入图片描述
最简单方法 中序遍历得到二叉搜索树对应的数组,将每个值的频率存到字典中

class Solution:
    def findMode(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        self.res=defaultdict(int)
        def dfs(node):
            if not node:
                return 
            dfs(node.left)
            self.res[node.val]+=1
            dfs(node.right)
        dfs(root)
        res=sorted(self.res.items(),key=lambda x:x[1],reverse=True)
        maxium=res[0][1]
        ans=[]
        for i,v in res:
            if v==maxium:
                ans.append(i)
        return ans

不使用额外空间:利用中序遍历,判断与前一个值是否相等,并且记录最大频率

class Solution:
    def findMode(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        self.cur=0
        self.pre=float('inf')
        self.maxnum=0
        self.res=[]
        def dfs(node):
            if not node:
                return 
            dfs(node.left)
            if node.val==self.pre:
                self.cur+=1
            else:
                self.cur=1
            if self.cur==self.maxnum:
                self.res.append(node.val)
            if self.cur>self.maxnum:
                self.res=[node.val]
                self.maxnum=self.cur
            self.pre=node.val
            dfs(node.right)
        dfs(root)
        return self.res

99 恢复二叉搜索树
在这里插入图片描述
在这里插入图片描述
时间空间复杂度都是O(n)

这里被交换了两个节点,因此中序遍历是一个几乎排好序的数组,其中有两个元素被交换。识别排序数组中两个交换元素是可以在线性时间内解决的经典问题。

def find_two_swapped(nums: List[int]) -> (int, int):
    n = len(nums)
    x = y = -1
    for i in range(n - 1):
        if nums[i + 1] < nums[i]:
            y = nums[i + 1]
            # first swap occurence
            if x == -1:     
                x = nums[i]
            # second swap occurence
            else:           
                break
    return x, y

中序遍历递归:找到需要被交换的两个节点
在这里插入图片描述
若要找到交换的节点,就记录中序遍历中的最后一个节点 pred(即当前节点的前置节点),并与当前节点的值进行比较。如果当前节点的值小于前置节点 pred 的值,说明该节点是交换节点之一。
交换的节点只有两个,因此在确定了第二个交换节点以后,可以终止遍历。

中序遍历顺序是 4,2,3,1,我们只要找到节点4和节点1交换顺序即可!
这里我们有个规律发现这两个节点:
第一个节点,是第一个按照中序遍历时候前一个节点大于后一个节点,我们选取前一个节点,这里指节点4;
第二个节点,是在第一个节点找到之后, 后面出现前一个节点大于后一个节点,我们选择后一个节点,这里指节点1;

class Solution:
    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        def dfs(node):
            nonlocal x,y,pre
            if not node:
                return 
            dfs(node.left)
            #因为只有两个节点被错误交换,中序遍历只需要判断是否大于前一个值
            if pre and node.val<pre.val:
                y=node
                if not x:
                    x=pre
                else:
                    return 
            pre=node
            dfs(node.right)
        x=y=pre=None
        dfs(root)
        x.val,y.val=y.val,x.val
        return root

迭代:

class Solution:
    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        if not root:
            return 
        x=y=pre=None
        stack=[]
        node=root
        while stack or node:
            while node:
                stack.append(node)
                node=node.left
            node=stack.pop()
            if pre and node.val<pre.val:
                y=node
                if x is None:
                    x=pre
            pre=node
            node=node.right
        x.val,y.val=y.val,x.val
        return root
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值