一、学习视频
二、视频跟练代码
题目:
解题:
(1)递归。按照灵神的说法这个叫做前序遍历。
# 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 isValidBST(self, root: Optional[TreeNode]) -> bool:
# 递归
# 递归左右范围
def f(node, l, r) -> bool:
if node is None:
return True
x = node.val
if x <= l or x >= r:
return False
leftBool = f(node.left, l, x) #缩短右边范围,左边范围不变,而不是无限
rightBool = f(node.right, x, r)
return leftBool and rightBool
return f(root, -inf, inf)
(2)中序遍历。来自视频。
非调用自身写法
# 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 isValidBST(self, root: Optional[TreeNode]) -> bool:
# 中序遍历,不调用自身
# 节点值从小到大
pre = -inf
def f(node) -> bool:
nonlocal pre
if node is None:
return True
if not f(node.left):
return False
if node.val <= pre: #注意是小于等于,中序遍历
return False
pre = node.val #遍历下一个节点,更新pre
return f(node.right)
return f(root)
调用自身写法。来自视频代码。
# 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:
pre = -inf
def isValidBST(self, root: Optional[TreeNode]) -> bool:
# 中序遍历,调用自身
if root is None:
return True
if not self.isValidBST(root.left):
return False
# if root.val <= Solution.pre:
if root.val <= self.pre: #注意是小于等于,中序遍历
return False
# Solution.pre = root.val
self.pre = root.val #遍历下一个节点,更新pre
return self.isValidBST(root.right)
注意:这里是self.pre而不是Solution.pre。前者是实例变量,随着实例不同值不同;后者是类变量,随着实例不同值相同,是同一个变量。
(3)后序遍历。来自视频。
# 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 isValidBST(self, root: Optional[TreeNode]) -> bool:
# 后序遍历
# 返回节点数字范围,归
# 拿左节点的最大值,右节点的最小值
def f(node):
if node is None:
return inf, -inf
l_min, l_max = f(node.left)
r_min, r_max = f(node.right)
x = node.val
if l_max < x < r_min:
return min(l_min, x), max(r_max, x)
else:
return -inf, inf
return True if (f(root)) != (-inf, inf) else False
三、课后作业练习
(1)递归
前序遍历,类似二分。
# 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 searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
# 递归,前序遍历,类似二分
# 注意是二叉搜索树
if root is None:
return None
x = root.val
if x == val:
return root
elif x > val:
left = self.searchBST(root.left, val)
return left if left else None
else:
right = self.searchBST(root.right, val)
return right if right else None
参考官方题解(. - 力扣(LeetCode)),空可以不用返回。对上一代码进行优化:
# 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 searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
# 递归,前序遍历,类似二分
# 注意是二叉搜索树
if root is None:
return None
x = root.val
if x == val:
return root
return self.searchBST(root.left if x > val else root.right, val)
或写成一行,来自题解(. - 力扣(LeetCode))。
(2)迭代。来自官方题解。
# 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 searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
# 迭代
while root:
if val == root.val:
return root
root = root.left if root.val > val else root.right
return None
(1)中序遍历
# 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:
pre = -inf
minGap = inf
def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
# 中序遍历
# 二叉搜索树中序遍历严格递增
if root is None: #为空差值为正无穷
return inf
leftGap = self.getMinimumDifference(root.left)
self.minGap = min(self.minGap, leftGap, root.val - self.pre) #更新最小gap
self.pre = root.val #更新上一节点
self.getMinimumDifference(root.right) #遍历右
return self.minGap
(2)中序遍历+迭代器。来自题解(. - 力扣(LeetCode))。
# 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 getMinimumDifference(self, root: Optional[TreeNode]) -> int:
# 中序遍历+迭代器
def f(node): #迭代器
if not node:
return
yield from f(node.left)
yield node.val
yield from f(node.right)
return min(b - a for a, b in pairwise(f(root)))
其中yield用来产生生成器,学习链接:http://t.csdnimg.cn/j9tDx。
下段来自chatgpt:
yield from
是 Python 3.3 引入的语法,用于简化生成器函数(generator function)中的 yield 语句的嵌套。它的作用是将一个可迭代对象(比如另一个生成器)中产生的值直接传递给外层的生成器。
使用迭代器进行中序遍历。
pairwise函数相关解释,下段来自chatgpt:
pairwise
函数通常用于在一个序列中生成相邻的元素对。这个函数在给定一个序列时,会返回一个迭代器,每次产生相邻的两个元素组成的元组。这样可以方便地在迭代中处理相邻元素。
使用pairwise函数对中序遍历后的迭代器相邻两项进行相减。
2024.4.8续:
(1)中序遍历数组+排序。将中序遍历结果用数组存起来。
# 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 closestNodes(self, root: Optional[TreeNode], queries: List[int]) -> List[List[int]]:
# 中序遍历数组+排序
# 二叉搜索树的中序遍历相当于有序数组,得到数组
tree = []
def Tree(node):
if node is None:
return
Tree(node.left)
tree.append(node.val)
Tree(node.right)
Tree(root)
treeN = len(tree)
n = len(queries)
ans = [0] * n
idx = list(range(n))
idx.sort(key=lambda x : queries[x])
treeIndex = 0
for i in idx:
target = queries[i]
mn, mx = -1, -1
while treeIndex < treeN:
mx = tree[treeIndex]
if mx >= target:
if mx == target:
mn = target
elif treeIndex > 0:
mn = tree[treeIndex - 1]
break
treeIndex += 1
if treeIndex == treeN:
mn, mx = tree[-1], -1
ans[i] = [mn, mx]
return ans
(2) 中序遍历数组+二分查找。来自灵神题解(. - 力扣(LeetCode))。有一瞬间脑子里闪过了使用二分,但仅仅只是一瞬间,还是不够熟练啊。
# 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 closestNodes(self, root: Optional[TreeNode], queries: List[int]) -> List[List[int]]:
# 中序遍历数组+二分查找
tree = []
def Tree(node):
if node is None:
return
Tree(node.left)
tree.append(node.val)
Tree(node.right)
Tree(root)
treeN = len(tree)
# 二分过程
ans = []
for q in queries:
idx = bisect.bisect_left(tree, q)
mx = tree[idx] if idx < treeN else -1
if idx == treeN or tree[idx] != q:
idx -= 1
mn = tree[idx] if idx >= 0 else -1
ans.append([mn, mx])
return ans
(1)中序遍历+哈希表(需额外空间)
# 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 findMode(self, root: Optional[TreeNode]) -> List[int]:
# 中序遍历+哈希表(需额外空间)
hash = {}
def tree(node):
if node is None:
return
tree(node.left)
x = node.val
hash[x] = hash.get(x, 0) + 1
tree(node.right)
tree(root)
maxcnt = 0
ans = []
for k, v in hash.items():
if v > maxcnt: #更新
ans = [k]
maxcnt = v
elif v == maxcnt:
ans.append(k)
return ans
(2)直接中序遍历。参考官方题解(. - 力扣(LeetCode))。
# 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 findMode(self, root: Optional[TreeNode]) -> List[int]:
# 直接中序遍历
curValue, maxCnt, curCnt = -10**5-1, 0, 0
ans = []
def f(node):
nonlocal ans
nonlocal curValue, maxCnt, curCnt
if node is None:
return
f(node.left)
x = node.val
# 更新curCnt
if x == curValue:
curCnt += 1
else:
curValue = x # 更新curValue
curCnt = 1
# 更新maxCnt
if curCnt == maxCnt:
ans.append(curValue)
if curCnt > maxCnt:
ans = [curValue]
maxCnt = curCnt
f(node.right)
f(root)
return ans
下面列举几个博主在写该题时出现的问题:
- 记录的是上一个节点的值与次数。我本来使用的方法是记录上一个节点,认为这样更新ans列表才不会出错。写出代码后,没有报错,但是结果不一样,仔细一想,这样的话没有包括最后遍历的那个节点,看完官方题解后发现,记录当前节点才是正确的。
- 那么为什么记录当前节点是正确的呢?博主之前认为,当curCnt == maxCnt时,在当前节点就将当前节点添加到ans中不太正确。后来仔细思考后发现,在curCnt == maxCnt的下一个节点处会出现两种情况1.等于上一个,那么此时curCnt > maxCnt,会对ans进行更新,原来的数组就不需要了,添加的值也就不会影响;2.不等于上一个,那么上一个节点curCnt == maxCnt,不会再增加,而且已经被添加进去了。所以在当前节点就将当前节点添加到ans中是没有问题的。
- 在解题时,我还想到使用之前的做法,将这三个参数作为函数参数传入或者作为返回值,这样想在这里是有问题的。首先作为函数参数传入的值是“递”的“东西”,返回值是“归”的“东西”,我们需要统一进行“递”传递东西,再“归”返回我们想要的东西。但是此题使用的是中序遍历,中序遍历先“递”左子树、“归”左子树,再“递”右子树、“归”右子树,这里是没有办法进行统一递归的,传参也比较困难。
2024.4.21续:
(3)Morris中序遍历,参考官方题解。
不会这个算法,去学习了一下。学习笔记为:http://t.csdnimg.cn/SWasY
# 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 findMode(self, root: Optional[TreeNode]) -> List[int]:
# Morris中序遍历
curValue, maxCnt, curCnt = -10**5-1, 0, 0
ans = []
def update(x: int):
nonlocal curValue, maxCnt, curCnt
nonlocal ans
# 更新curCnt
if x == curValue:
curCnt += 1
else:
curValue = x # 更新curValue
curCnt = 1
# 更新maxCnt
if curCnt == maxCnt:
ans.append(curValue)
if curCnt > maxCnt:
ans = [curValue]
maxCnt = curCnt
# Morris中序遍历过程
cur = root
while cur:
if not cur.left:
update(cur.val) #中序到达中间节点时更新
cur = cur.right
else:
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
if pre.right is None:
pre.right = cur
cur = cur.left
else:
pre.right = None #删除线索
update(cur.val) #中序到达中间节点时更新
cur = cur.right
return ans
(1)递归,中序遍历。
# 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 kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# 递归,中序遍历
ans = -1
def f(node):
nonlocal ans, k
if node is None:
return
f(node.left)
if k < 0:
return
elif k == 1:
# 找到了
k = -1
ans = node.val
return
else:
k -= 1
f(node.right)
f(root)
return ans
(2)Morris中序遍历
# 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 kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# # 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 kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# Morris中序遍历
cur = root
while cur:
if cur.left is None:
if k == 1:
return cur.val
else:
k -= 1
cur = cur.right
else:
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
if pre.right is None:
pre.right = cur
cur = cur.left
else:
if k == 1:
return cur.val
else:
k -= 1
pre.right = None
cur = cur.right
cur = root
while cur:
if cur.left is None:
if k == 1:
return cur.val
else:
k -= 1
cur = cur.right
else:
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
if pre.right is None:
pre.right = cur
cur = cur.left
else:
if k == 1:
return cur.val
else:
k -= 1
pre.right = None
cur = cur.right
(3)通过栈实现中序遍历,来自官方题解(. - 力扣(LeetCode))。
# 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 kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# 通过栈实现中序遍历
stack = []
while root or stack:
while root:
# 将当前节点及其所有左节点全部压入栈
stack.append(root)
root = root.left
root = stack.pop()
k -= 1
if k == 0:
return root.val
# 遍历右子树
root = root.right
(4)生成器中序遍历,来自题解(. - 力扣(LeetCode))。
# 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 kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# 生成器中序遍历
def f(node):
if node is None:
return
yield from f(node.left)
yield node.val
yield from f(node.right)
it = f(root) #将生成器对象赋值给it
for _ in range(k - 1):
# 消耗前k-1个数
next(it)
return next(it)
后序遍历
# 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 maxSumBST(self, root: Optional[TreeNode]) -> int:
# 后序遍历
# 边验证二叉搜索树,边更新
# 返回值为树的左边界,右边界,子树和
maxSum = 0
def f(node):
nonlocal maxSum
if node is None:
return inf, -inf, 0
lmin, lmax, ls = f(node.left)
rmin, rmax, rs = f(node.right)
x = node.val
if lmax < x < rmin:
# 是二叉搜索树
curSum = x + ls + rs
maxSum = max(maxSum, curSum)
return min(lmin, x), max(rmax, x), curSum
else:
return -inf, inf, 0
f(root)
return maxSum
不会,以下均来自灵神题解(. - 力扣(LeetCode))。
(1)递归
# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 递归
# 根据前序和中序性质
# if preorder is None: # 错误的
if not preorder:
return None
left_size = inorder.index(preorder[0]) #左子树大小
left = self.buildTree(preorder[1:1 + left_size], inorder[:left_size])
right = self.buildTree(preorder[1 + left_size:], inorder[left_size+1:])
return TreeNode(preorder[0], left, right) #构造树
其中博主在使用if preorder is None为条件时报错,因为此处preorder是列表。和if not preorder区别如下,来自chatgpt:
在 Python 中,`if not preorder` 和 `if preorder is None` 在处理条件时有一些微妙的区别。
1. **`if not preorder`:** 这个条件检查 preorder 是否为 False 或者是否为空。如果 preorder 是一个空列表、空字符串、0、None 等 Python 中被视为 False 的值,那么条件就会成立,执行其下的代码块。但如果 preorder 是一个非空的列表,即使列表里的元素全部是 0 或者空字符串等被视为 False 的值,这个条件也会被视为 True,因为列表本身不是空的。
2. **`if preorder is None`:** 这个条件则明确地检查 preorder 是否为 None。如果 preorder 是 None,条件成立,执行其下的代码块。如果 preorder 是一个空列表、空字符串、0 或者其他被视为 False 的值,这个条件也不成立,因为它不是 None。
因此,如果你想要检查 preorder 是否为空列表或者 None,那么 `if not preorder` 是更适合的选择。如果你只想检查 preorder 是否为 None,那么使用 `if preorder is None` 更明确。
(2)递归优化
# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# 递归优化
# 哈希表记录中序下标
index = {x: i for i, x in enumerate(inorder)}
def f(pre_l: int, pre_r: int, in_l: int, in_r: int):
# 左闭右开,如同切片
# 将切片列表改为指针
if pre_l == pre_r: #空节点
return None
left_size = index[preorder[pre_l]] - in_l #中序根节点下标 - 中序左边界
left = f(pre_l + 1, pre_l + 1 + left_size, in_l, in_l + left_size)
right = f(pre_l + 1 + left_size, pre_r, in_l + left_size + 1, in_r)
return TreeNode(preorder[pre_l], left, right)
return f(0, len(preorder), 0, len(inorder))
做法和前一道题类似
(1)递归,切片。
# 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 buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 递归,切片
if not postorder:
return None
left_size = inorder.index(postorder[-1])
left = self.buildTree(inorder[:left_size], postorder[:left_size])
right = self.buildTree(inorder[left_size + 1:], postorder[left_size:-1])
return TreeNode(postorder[-1], left, right)
(2)递归优化,哈希表+左右指针。
# 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 buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 递归优化,哈希表+左右指针
index = {x: i for i, x in enumerate(inorder)}
def f(in_l: int, in_r: int, post_l: int, post_r: int):
if post_l == post_r:
return None
left_size = index[postorder[post_r-1]] - in_l #注意右边是开区间,需要减一
left = f(in_l, in_l + left_size, post_l, post_l + left_size)
right = f(in_l + left_size + 1, in_r, post_l + left_size, post_r - 1)
return TreeNode(postorder[post_r-1], left, right)
return f(0, len(inorder), 0, len(postorder))
跟前面差不多,根节点为前序第一个(且为后序最后一个)。另外,题目中为什么说答案不唯一呢?是因为不含中序遍历是无法唯一确定二叉树的(前+中、后+中可以,前+后不行)。
(1)递归
# 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 constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 递归
# 后序的倒数第二个节点是根节点的右节点
# 以此将左右子树分开即可
if not preorder or not postorder:
return None
if len(postorder) == 1:
return TreeNode(postorder[-1], None, None)
left_size = preorder.index(postorder[-2]) - 1
left = self.constructFromPrePost(preorder[1: left_size + 1], postorder[: left_size])
right = self.constructFromPrePost(preorder[left_size + 1:], postorder[left_size: -1])
return TreeNode(postorder[-1], left, right)
(2)递归优化
# 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 constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 递归优化
# 哈希表
index = {x: i for i, x in enumerate(preorder)}
def f(pre_l, pre_r, post_l, post_r):
if post_l == post_r:
return None
if (post_r - post_l) == 1:
return TreeNode(postorder[post_r - 1], None, None)
left_size = index[postorder[post_r - 2]] - pre_l - 1 #右开
left = f(pre_l + 1, pre_l + left_size + 1, post_l, post_l + left_size)
right = f(pre_l + left_size + 1, pre_r, post_l + left_size, post_r - 1)
return TreeNode(postorder[post_r - 1], left, right)
return f(0, len(preorder), 0, len(postorder))
10.1110. 删点成林
后序遍历
# 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 delNodes(self, root: Optional[TreeNode], to_delete: List[int]) -> List[TreeNode]:
# 后序遍历
def f(node) -> (Optional[TreeNode], List[TreeNode]):
if not node:
return None, []
left_tree, left_list = f(node.left)
right_tree, right_list = f(node.right)
if node.val in to_delete:
if left_tree:
left_list.append(left_tree)
if right_tree:
right_list.append(right_tree)
return None, left_list + right_list
else:
node.left = left_tree
node.right = right_tree
return node, left_list + right_list
root_tree, root_list = f(root)
if root_tree:
root_list.append(root_tree)
return root_list
参考灵神题解(. - 力扣(LeetCode)),对上述代码进行优化,优化方面如下:
- 如何快速查找node.val是不是在to_delete:
我之前还想过使用二分查找函数,但是对于这道题来说还是感觉不太方便。使用集合实现快速查找,下段来自chatgpt:
在 Python 中,使用集合(set)来存储数据可以实现快速的查找操作。这是因为集合是基于哈希表实现的数据结构,具有 O(1) 的平均时间复杂度来执行查找操作。所以,如果你有一个列表 to_delete
,并且你想要快速检查某个元素是否在其中,可以将它转换为集合,然后使用 in
运算符来进行查找。
- 把返回列表改为维护非局部变量。
- 将返回的左右树节点直接赋值为根节点的左右节点。
修改代码如下:
# 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 delNodes(self, root: Optional[TreeNode], to_delete: List[int]) -> List[TreeNode]:
# 后序遍历优化
ans = []
s = set(to_delete)
def f(node) -> Optional[TreeNode]:
if not node:
return None
node.left = f(node.left)
node.right = f(node.right)
if node.val not in s:
return node
if node.left:
ans.append(node.left)
if node.right:
ans.append(node.right)
return None
if f(root):
ans.append(root)
return ans
完
感谢你看到这里!一起加油吧!