算法打卡
二叉树的表示方式
使用数组来表示二叉树是一种常见的方法,尤其是对于完全二叉树或满二叉树来说,这种表示方法非常高效和直观。在这种表示法中,二叉树的每个节点都对应数组中的一个位置,数组的索引用于表示节点之间的父子关系。
基本规则
- 根节点位于数组的第一个位置,即索引
0
。 - 对于数组中任意位置
i
的元素,其左子节点的位置为2*i + 1
,右子节点的位置为2*i + 2
。 - 同样,对于数组中任意位置
i
的元素,其父节点的位置为(i-1) / 2
(向下取整)。
例子
考虑一个简单的二叉树如下:
1
/ \
2 3
/ \
4 5
这棵树可以用数组 [1, 2, 3, 4, 5]
来表示。具体对应关系如下:
- 根节点
1
在数组位置0
。 1
的左子节点2
在位置2*0+1 = 1
。1
的右子节点3
在位置2*0+2 = 2
。2
的左子节点4
在位置2*1+1 = 3
。2
的右子节点5
在位置2*1+2 = 4
。
完全二叉树与非完全二叉树
这种表示方法对于完全二叉树是最有效的,因为在完全二叉树中,不存在跳过的数组位置。对于非完全二叉树,这种方法也可以使用,但可能会导致数组中存在一些未使用的位置(用一些特殊值表示,如null
),这些位置代表了虚拟的空节点,以保持父子节点间正确的位置关系。
例如,对于一个非完全二叉树:
1
/
2
/ \
3 4
这棵树可以用数组 [1, 2, null, 3, 4]
来表示,其中 null
位置代表原本应该是第一个节点右子节点的位置,但在这个树中不存在。
使用数组来表示二叉树时,可以有效地访问和修改节点,尤其是在实现某些数据结构如二叉堆时非常有用。
二叉树遍历基本概念
假设有二叉树的两种遍历结果 [3,9,20,15,7]
(前序遍历结果)和 [9,3,15,20,7]
(中序遍历结果)做的那样。
理解前序和中序遍历
首先,想象一下你有一个箱子里装满了玩具,你要按照一定的规则来拿出这些玩具。
- 前序遍历就像是先拿出最上面的玩具(我们称它为“根”玩具),然后从左边开始拿出下面的玩具,一直到你不能再向左拿时,你再开始从右边拿。
- 中序遍历则是你先从最左边开始拿玩具,直到你不能再拿(到达最左边的玩具),然后回到上一个能向右拿的玩具,再继续向右拿。
如何构造二叉树
-
找到根节点:在前序遍历中,第一个数字总是“根”玩具,也就是整个树的最顶部。在我们的例子中,
3
是根节点。 -
分割点:在中序遍历中找到这个根节点(
3
),它会告诉我们哪些数字在它的左边(9
),哪些在右边(15, 20, 7
)。这意味着左边的都在左子树中,右边的都在右子树中。 -
构造左子树和右子树:用相同的方法,我们用左边的数字(
9
)和右边的数字(15, 20, 7
)分别构造左子树和右子树。在我们的例子中,9
就直接成为了左子节点,因为它没有其他的左边或右边的数字了。对于右边的数字,20
成为右子树的根节点,15
和7
分别成为它的左子节点和右子节点。 -
重复步骤:对于每个子树,我们重复这个过程,直到所有的数字都被用完。
结果
通过这个方法,我们就可以构建出一个二叉树。最后的结构是这样的:
3
/ \
9 20
/ \
15 7
3
是根节点。9
是3
的左子节点。20
是3
的右子节点,而15
和7
分别是20
的左子节点和右子节点。
打卡题目
根据前序 中序遍历结果推断出二叉树结构
描述:给定一棵二叉树的前序遍历结果 preorder 和中序遍历结果 inorder。
要求:构造出该二叉树并返回其根节点。
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def buildTree(preorder, inorder):
if not preorder or not inorder:
return None
# 前序遍历的第一个值是根节点
root_val = preorder[0]
root = TreeNode(root_val)
# 在中序遍历中找到根节点的位置,以此来分割左子树和右子树
index = inorder.index(root_val)
# 递归构造左子树和右子树
root.left = buildTree(preorder[1:index + 1], inorder[:index])
root.right = buildTree(preorder[index + 1:], inorder[index + 1:])
return root
# 用于展示二叉树的辅助函数
def serialize(root):
"""Encodes a tree to a single string."""
if not root:
return "null,"
left_serialized = serialize(root.left)
right_serialized = serialize(root.right)
return str(root.val) + "," + left_serialized + right_serialized
# 测试数据
preorder = [3, 9, 20, 15, 7]
inorder = [9, 3, 15, 20, 7]
# 构造二叉树并序列化以便显示结果
root = buildTree(preorder, inorder)
prialanced(root.left) and self.isBalanced(root.right)
判断二叉搜索树
描述:给定一个二叉树的根节点 root。
要求:判断其是否是一个有效的二叉搜索树。
说明:
二叉搜索树特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def isValidBST(root):
def validate(node, low=-float('inf'), high=float('inf')):
# 空节点默认是有效的
if not node:
return True
# 当前节点的值必须在有效范围内
if not (low < node.val < high):
return False
# 递归检查左子树和右子树
return (validate(node.left, low, node.val) and
validate(node.right, node.val, high))
return validate(root)
# 构造示例二叉树 [2,1,3]
root = TreeNode(2)
root.left = TreeNode(1)
root.right = TreeNode(3)
# 检查是否是有效的二叉搜索树
isValidBST(root)
计算平衡二叉树 --> 左右子树高度差相差1
描述:给定一个二叉树的根节点 root。
要求:判断该二叉树是否是高度平衡的二叉树。
说明:
高度平衡二叉树:二叉树中每个节点的左右两个子树的高度差的绝对值不超过
。
class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool:
def height(root):
# 遍历到末尾返回0
if not root:
return 0
# 递归获取高度
return max( height(root.left) , height(root.right) ) + 1
if not root:
return True
# 递归计算所有的节点都是平衡二叉树
return abs(height(root.left) - height(root.right))<=1 and self.isBalanced(root.left) and self.isBalanced(root.right)
路径总和2
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]:
ret = list()
path = list()
def dfs(root: TreeNode, targetSum: int):
if not root:
return
path.append(root.val)
targetSum -= root.val
if not root.left and not root.right and targetSum == 0:
ret.append(path[:])
dfs(root.left, targetSum)
dfs(root.right, targetSum)
path.pop()