[英雄星球六月集训LeetCode解题日报] 第18日 树

日报

  • 树的难易度方差极大。。

题目

一、 1361. 验证二叉树

链接: 1361. 验证二叉树

1. 题目描述

二叉树上有 n 个节点,按从 0 到 n - 1 编号,其中节点 i 的两个子节点分别是 leftChild[i] 和 rightChild[i]。

只有 所有 节点能够形成且 只 形成 一颗 有效的二叉树时,返回 true;否则返回 false。

如果节点 i 没有左子节点,那么 leftChild[i] 就等于 -1。右子节点也符合该规则。

注意:节点没有值,本问题中仅仅使用节点编号。

2. 思路分析

简直是面向用例编程,累死我了。
用并查集通过父节点合并每个孩子进家族。

  • 合并时,如果发现孩子有父亲,那么冲突。(每个节点只能有一个父亲)
  • 如果发现孩子本身在父亲的树上,要么它是根节点(有环),要么它是别人的孩子(冲突)。
  • 最后检查家族数量,只能为1
3. 代码实现
class Solution:
    def validateBinaryTreeNodes(self, n: int, leftChild: List[int], rightChild: List[int]) -> bool:        
        # 并查集计算树根,保证只能有一个树根
        # 每个节点并入时,不能有父亲
        fathers = list(range(n))
        def find_father(x):
            if fathers[x] != x:
                fathers[x] = find_father(fathers[x])
            return fathers[x]
        def union(x,y):
            x = find_father(x)
            y = find_father(y)
            if x == y:
                return False
            fathers[x] = y
            return True
        
        for i in range(n):
            l = leftChild[i]
            if l != -1:
                if l != find_father(l):  # l已经有父亲
                    return False
                if not union(l,i):  # 并入失败,说明l已经在i所在树上,要么他是根(有环),要么他是别人的儿子(冲突)
                    return False
            r = rightChild[i]
            if r != -1:
                if r != find_father(r):  # 已经有父亲
                    return False
                if not union(r,i):
                    return False
                
        # print(set(uf.find_father(f) for f in uf.fathers))
        return len(set(find_father(f) for f in fathers)) == 1

二、 1367. 二叉树中的列表

链接: 1367. 二叉树中的列表

1. 题目描述

给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。

如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False 。

一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。

2. 思路分析

树上的模式匹配。

  1. 暴搜,以每个节点为链表头向下搜索,注意设置双层搜索。
  2. 正好练习树上KMP,求出next数组后进行模式串指针的转移。
  3. 剪枝:
    • 预处理子树高度,搜索时子树高度不够,则返回。
    • 实测对kmp是负优化。
      在这里插入图片描述
3. 代码实现

树上KMP

class Solution:
    def isSubPath(self, head: ListNode, root: TreeNode) -> bool:
        path = []
        while head:
            path.append(head.val)
            head = head.next
        n = len(path)
        def get_next(p):
            n = len(p)
            nxt = [0]*n
            nxt[0] = -1
            j,k=0,-1
            while j < n-1:
                if k == -1 or p[j] == p[k]:
                    j+=1
                    k+=1
                    if p[j] == p[k]:
                        nxt[j] = nxt[k]
                    else:
                        nxt[j] = k 
                else:
                    k = nxt[k]
           
            return nxt
        nxt = get_next(path)
        # print(nxt)
        
        def dfs_kmp(tree, j):
            if j == n:
                return True
            if not tree:
                return False
            if j == -1 or tree.val == path[j]:
                return dfs_kmp(tree.left,j+1) or dfs_kmp(tree.right,j+1)
            else:
                return dfs_kmp(tree,nxt[j]) 


        return dfs_kmp(root,0)

双层暴搜+剪枝

class Solution:
    def isSubPath(self, head: ListNode, root: TreeNode) -> bool:
        n,cur = 0,head
        while cur:
            n+=1
            cur=cur.next
        def find_height(root):  # 预处理子树高度
            if not root:
                return 0
            root.height = 1+max(find_height(root.left),find_height(root.right))
            return root.height
        find_height(root)
            
        def match(tree,lst,m):
            if not lst:  # 链表用完了说明True
                return True
            if not tree:  # 树用完了链表没完说明False
                return False
            if tree.height < m:  # 如果当前树高不足以支撑剩余的链表长度,False
                return False
            if tree.val != lst.val:
                return False
            return match(tree.left,lst.next,m-1) or match(tree.right,lst.next,m-1)

        def is_sub(tree):  # 以tree根节点为链头
            if not tree:
                return False
            return match(tree,head,n) or is_sub(tree.left) or is_sub(tree.right)        
   
        return is_sub(root)

三、 1457. 二叉树中的伪回文路径

链接: 1457. 二叉树中的伪回文路径

1. 题目描述

给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的,当它满足:路径经过的所有节点值的排列中,存在一个回文序列。

请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。

2. 思路分析

因为定义是路径上所有值的排列中存在即可,因此可以随便摆,那么我们计数每个数,出现奇数次数的数字只能有一个,就算伪回文。

  • 那么dfs计数路径上的数,回溯即可。
  • 额外的:由于计数只看奇偶,因此可以用异或来代替计数。
  • 本题特别的:
    • 由于val范围[1-9],因此计数器字典也可以直接状压成一个数t。
    • 这样is_good判断就是t中位1不能超过1个。
  • 实测位运算有一定优化。
    在这里插入图片描述
3. 代码实现
class Solution:
    def pseudoPalindromicPaths (self, root: TreeNode) -> int:
        """因为定义是路径上所有值的排列中存在即可,因此可以随便摆,那么我们计数每个数,出现奇数次数的数字只能有一个,就算伪回文。
        额外的:由于计数只看奇偶,因此可以用异或来代替计数。
        本题特别的:
            由于val范围[1-9],因此计数器字典也可以直接状压成一个数t。
            这样is_good判断就是t中位1不能超过1个。
        """
        ans = 0
        def dfs(root,t):
            if not root:
                return
            t ^= 1<<root.val
            if not root.left and not root.right:              
                if t == 0 or (t&(t-1))==0:
                    nonlocal ans
                    ans += 1
            dfs(root.left,t)
            dfs(root.right,t)
            t ^= 1<<root.val
        dfs(root,0)
        return ans

四、 1373. 二叉搜索子树的最大键值和

链接: 1373. 二叉搜索子树的最大键值和

1. 题目描述

给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。

二叉搜索树的定义如下:

任意节点的左子树中的键值都 小于 此节点的键值。
任意节点的右子树中的键值都 大于 此节点的键值。
任意节点的左子树和右子树都是二叉搜索树。

2. 思路分析

题目其实不难,主要是每层子树pushup反馈的信息有四个:

  1. 子树是否是BST。
  2. 子树权和。
  3. 左子树需要最大值,右子树需要最小值。
    但是python可以返回元组,所以难度突然下降。
    我们令dfs的返回值上述四个值,然后进行后根遍历:
    当左右子树都是BST,且当前值大于左子树最大值,且当前值小于右子树最小值,则更新答案。

在这里插入图片描述

3. 代码实现
class Solution:
    def maxSumBST(self, root: Optional[TreeNode]) -> int:
        inf = 4000000

        ans = 0
        def dfs(root):
            if not root:  # 空树是搜索树
                return True,0,inf,-inf  # 是否是搜索树,和,最小值,最大值            
            
            lf,l,lmin,lmax = dfs(root.left)
            rf,r,rmin,rmax = dfs(root.right)
            if lf and rf and root.val > lmax and root.val<rmin:
                ret = root.val + l+ r
                nonlocal ans
                ans = max(ans,ret)
                return True,ret,lmin if root.left else root.val, rmax if root.right else root.val

            return False,0 ,0,0
        dfs(root)
        return ans

参考链接

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值