leetcode(11)二叉树的属性系列题目
104. 二叉树的最大深度
递归法
可以使用前序遍历(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
而根节点的高度就是二叉树的最大深度
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
return self.getHeight(root)
def getHeight(self, root):
if not root:
return 0
leftHeight = self.getHeight(root.left)
rightHeight = self.getHeight(root.right)
return max(leftHeight, rightHeight) + 1
111. 二叉树的最小深度
递归法
class Solution:
def minDepth(self, root: TreeNode) -> int:
if not root:
return 0
if not root.left and not root.right:
return 1
res = float('inf')
if root.left:
res = min(res, self.minDepth(root.left))
if root.right:
res = min(res, self.minDepth(root.right))
return res + 1
222. 完全二叉树的节点个数
迭代法,按照普通二叉树的方法
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
res = 0
from collections import deque
que = deque([root])
while que:
size = len(que)
for _ in range(size):
cur = que.popleft()
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
res += 1
return res
递归法,利用完全二叉树的性质,求深度
只有两种情况:
-
情况一:就是满二叉树,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
-
情况二:最后一层叶子节点没有满,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
left = root.left
right = root.right
#这里初始为0是有目的的,为了下面求指数方便
leftHeight = 0
rightHeight = 0
#求左子树深度
while left:
left = left.left
leftHeight += 1
#求右子树深度
while right:
right = right.right
rightHeight += 1
if leftHeight == rightHeight:
return (2 << leftHeight) - 1 #注意(2<<1) 相当于2^2,所以leftHeight初始为0
return self.countNodes(root.left) + self.countNodes(root.right) + 1
110. 平衡二叉树
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
求深度适合用前序遍历,而求高度适合用后序遍历。
递归法,后序遍历
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if self.getHeight(root) != -1:
return True
else:
return False
def getHeight(self, root):
if not root:
return 0
leftHeight = self.getHeight(root.left)
rightHeight = self.getHeight(root.right)
if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) >1:
return -1
else:
return max(leftHeight, rightHeight) + 1
257. 二叉树的所有路径
需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。
递归法+隐形回溯
class Solution:
def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
if not root:
return []
path = ''
res = []
self.traversal(root, path, res)
return res
def traversal(self, root, path, res):
path += str(root.val)
# 若当前节点为leave,直接输出
if not root.left and not root.right:
res.append(path)
if root.left:
self.traversal(root.left, path + '->', res) # + '->' 是隐藏回溯
if root.right:
self.traversal(root.right, path + '->', res)
543. 二叉树的直径
注意:一条路径的长度为该路径经过的节点数减一,所以求直径(即求路径长度的最大值)等价于求路径经过节点数的最大值减一
而任意一条路径均可以被看作由某个节点为起点,从其左儿子和右儿子向下遍历的路径拼接得到
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.res = 0 # 0或1都可以
def depth(root):
# 访问到空节点了,返回0
if not root:
return 0
# 左儿子为根的子树的深度
leftDep = depth(root.left)
# 右儿子为根的子树的深度
rightDep = depth(root.right)
# 计算d_node即L+R+1 并更新res
self.res = max(self.res, leftDep + rightDep + 1)
# 返回该节点为根的子树的深度
return max(leftDep, rightDep) + 1
depth(root)
return self.res - 1
124. 二叉树中的最大路径和
注意:与543. 二叉树的直径 的区别是注意节点值为负的情况
class Solution:
def __init__(self):
self.maxSum = float('-inf')
def maxPathSum(self, root: Optional[TreeNode]) -> int:
def maxGain(root):
if not root:
return 0
# 递归计算左右子节点的最大贡献值
# 只有在最大贡献值大于 0 时,才会选取对应子节点
leftGain = max(maxGain(root.left), 0)
rightGain = max(maxGain(root.right), 0)
# 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
curGain = root.val + leftGain + rightGain
# 更新答案
self.maxSum = max(self.maxSum, curGain)
# 返回节点的最大贡献值
return root.val + max(leftGain, rightGain)
maxGain(root)
return self.maxSum
404. 左叶子之和
判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。
如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子
# 递归
class Solution:
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
left_val = self.sumOfLeftLeaves(root.left) # 左
right_val = self.sumOfLeftLeaves(root.right) # 右
cur_val = 0
if root.left and not root.left.left and not root.left.right:
cur_val = root.left.val
return cur_val + left_val + right_val # 中
513. 找树左下角的值
方法1:递归
如果需要遍历整棵树,递归函数就不能有返回值。如果需要遍历某一条固定路线,递归函数就一定要有返回值!
本题是要遍历整个树找到最深的叶子节点,需要遍历整棵树,所以递归函数没有返回值。
方法2:使用层序遍历,比递归要好理解的多!
只需要记录最后一行第一个节点的数值就可以了。
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
que = collections.deque([root])
res = 0
while que:
size = len(que)
for i in range(size):
if i == 0:
res = que[i].val # 更新为每一层最左边的元素
cur = que.popleft()
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
return res
112. 路径总和
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (236. 二叉树的最近公共祖先)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
# 精简版
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return root.val == targetSum
return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)
# 详细版
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
def dfs(root, remain):
if not root.left and not root.right and remain == 0:
return True
if root.left:
# remain -= root.left.val
if dfs(root.left, remain - root.left.val):
return True
# remain += root.left.val # 回溯
if root.right:
# remain -= root.right.val
if dfs(root.right, remain - root.right.val):
return True
# remain += root.right.val # 回溯
return False
if not root:
return False
return dfs(root, targetSum - root.val)
113. 路径总和 II
注意:与 112. 路径总和 的区别是要遍历整个树,找到所有路径,所以递归函数不要返回值!
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
def dfs(root, remain):
if not root.left and not root.right and remain == 0:
res.append(path[:])
if root.left:
path.append(root.left.val)
dfs(root.left, remain - root.left.val)
path.pop() # 回溯
if root.right:
path.append(root.right.val)
dfs(root.right, remain - root.right.val)
path.pop() # 回溯
if not root:
return []
res, path = [], []
path.append(root.val)
dfs(root, targetSum - root.val)
return res
236. 二叉树的最近公共祖先
注意:本题递归函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
在递归函数有返回值的情况下:
- 如果要搜索一条边,递归函数返回值不为空的时候,立刻返回
- 如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树写法:
left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;
- 如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解
- 如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or p == root or q == root:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
elif left:
return left
return right
235. 二叉搜索树的最近公共祖先
和 236. 二叉树的最近公共祖先 的区别是,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。
那么我们可以采用前序遍历(其实这里没有中节点的处理逻辑,遍历顺序无所谓了)。
在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭又闭)
那么如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)。
需要注意的是此时不知道p和q谁大,所以两个都要判断
注意:本题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q)
if root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
return root
106. 从中序与后序遍历序列构造二叉树
注意:中序数组大小一定是和后序数组的大小相同的。
中序数组被切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
# 第一步: 特殊情况讨论: 树为空. (递归终止条件)
if not inorder:
return None
# 第二步: 后序遍历的最后一个就是当前的中间节点.
root_val = postorder[-1]
root = TreeNode(root_val)
# 第三步: 找切割点.
sep = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
in_left = inorder[:sep]
in_right = inorder[sep + 1:] # +1是因为中间一个位置是root
# 第五步: 切割postorder数组. 得到postorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的.
pos_left = postorder[:len(in_left)]
pos_right = postorder[len(in_left):len(postorder) - 1] # -1是因为最后一个位置是root
# 第六步: 递归
root.left = self.buildTree(in_left, pos_left)
root.right = self.buildTree(in_right, pos_right)
return root
105. 从前序与中序遍历序列构造二叉树
根据中序左子树的个数确定前序左子树个数
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not inorder:
return None
root_val = preorder[0]
root = TreeNode(root_val)
sep = inorder.index(root_val)
in_left = inorder[:sep]
in_right = inorder[sep + 1:]
pre_left = preorder[1: len(in_left) + 1] # 右边是开区间
pre_right = preorder[len(in_left) + 1:]
root.left = self.buildTree(pre_left, in_left)
root.right = self.buildTree(pre_right, in_right)
return root
1008. 前序遍历构造二叉搜索树
直接找分界点,使用next更高效,是python的列表解析,相当于
def func():
for i, val in enumerate(preorder)
if val > root.val
return i
return len(preorder)
# 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 bstFromPreorder(self, preorder: List[int]) -> Optional[TreeNode]:
if not preorder: return
root = TreeNode(preorder[0])
div = next((i for i,v in enumerate(preorder) if v > preorder[0]), len(preorder))
root.left = self.bstFromPreorder(preorder[1:div])
root.right = self.bstFromPreorder(preorder[div:])
return root
根据搜索二叉树的性质,遍历数组,大的放l小的放r
class Solution:
def bstFromPreorder(self, preorder: List[int]) -> Optional[TreeNode]:
if not preorder: return
root = TreeNode(preorder[0])
l, r =[], []
for i in range(1, len(preorder)):
if preorder[i] < preorder[0]:
l.append(preorder[i])
else:
r.append(preorder[i])
root.left = self.bstFromPreorder(l)
root.right = self.bstFromPreorder(r)
return root
654. 最大二叉树
这回终于轮到我觉得自己妙到家了呜呜呜
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
if not nums:
return None
cur_val = max(nums)
cur_idx = nums.index(cur_val)
root = TreeNode(cur_val)
root.left = self.constructMaximumBinaryTree(nums[:cur_idx])
root.right = self.constructMaximumBinaryTree(nums[cur_idx + 1:])
return root
617. 合并二叉树
事实证明肌肉记忆是有用的呜呜呜虽然只是简单题
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if not root1 and not root2:
return None
elif not root1:
return root2
elif not root2:
return root1
root = TreeNode(root1.val + root2.val)
root.left = self.mergeTrees(root1.left, root2.left)
root.right = self.mergeTrees(root1.right, root2.right)
return root