[英雄星球六月集训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. 思路分析
树上的模式匹配。
- 暴搜,以每个节点为链表头向下搜索,注意设置双层搜索。
- 正好练习树上KMP,求出next数组后进行模式串指针的转移。
- 剪枝:
- 预处理子树高度,搜索时子树高度不够,则返回。
- 实测对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. 二叉搜索子树的最大键值和
1. 题目描述
给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。
二叉搜索树的定义如下:
任意节点的左子树中的键值都 小于 此节点的键值。
任意节点的右子树中的键值都 大于 此节点的键值。
任意节点的左子树和右子树都是二叉搜索树。
2. 思路分析
题目其实不难,主要是每层子树pushup反馈的信息有四个:
- 子树是否是BST。
- 子树权和。
- 左子树需要最大值,右子树需要最小值。
但是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