二叉树经典算法
二叉树里的双指针
双指针,就是定义了两个变量,在二叉树中有时候也需要至少定义两个变量才能解决问题;这两个指针可能针对一棵树,也可能针对两棵树;这些问题一般是与对称、反转和合并等类型有关。
判断两棵树是否相同
LeetCode100
https://leetcode.cn/problems/same-tree/description/
思路分析
这个貌似就是两个二叉树同时进行前序遍历,先判断根节点是否相同,如果相同再分别判断左右子节点是否相同,判断的过程中只要有一个不相同就返回false,如果全部相同才返回true
代码实现
# 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 isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
# 先判断根节点是否相同,再判断左右节点是否相同
if not p and not q:
return True
elif not p or not q:
return False
elif p.val != q.val:
return False
else:
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
对称二叉树
LeetCode101
https://leetcode.cn/problems/symmetric-tree/
思路分析
对称二叉树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wR9myKFe-1692018154373)(vx_images/257922505625700.png)]
递归判断两个子树的内侧(里侧)节点和外侧节点是否相等,一个树进行左右中遍历,一个树进行右左中遍历;
关键是如何比较和如何处理结束条件,单层递归的逻辑是处理左右节点都不为空,且数值相同的情况;
比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子
比较二叉树内侧是否对称:传入的是左节点的右孩子,右节点的左孩子
如果左右都对称就返回true,有一侧不对称就返回false
代码实现
# 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 isSymmetric(self, root: Optional[TreeNode]) -> bool:
def check(left, right):
if not left and not right:
return True
elif not left or not right:
return False
elif left.val != right.val:
return False
else:
return check(left.left, right.right) and check(left.right, right.left)
if not root:
return True
return check(root.left, root.right)
合并二叉树
LeetCode617
https://leetcode.cn/problems/merge-two-binary-trees/
合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
输入:
Tree1: Tree2:
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
思路分析
存在三种情况
如果两个二叉树的对应节点都为空,合并后节点为空
如果两个二叉树的对应节点只有一个为空,合并后节点为其中非空节点
如果两个二叉树的对应节点都不为空,合并后节点值为两个节点值相加
代码实现
# 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 mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if not root1:
return root2
elif not root2:
return root1
merged_node = TreeNode(root1.val + root2.val)
merged_node.left = self.mergeTrees(root1.left, root2.left)
merged_node.right = self.mergeTrees(root1.right, root2.right)
return merged_node
路径专题
二叉树的所有路径
LeetCode257
https://leetcode.cn/problems/binary-tree-paths/
思路分析
有几个叶子节点,就有几条路径
如何找到叶子节点?
深度优先搜索,从根节点开始一直找到叶子结点,这里可以先判断当前节点是不是叶子节点,再决定是不是向下走,如果是,增加一条路径
找到叶子节点时,如何获取当前叶子节点的完整路径?
增加一个String类型的变量,每个节点访问时先存到string中,到叶子节点时存储到集合里
代码实现
# 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 binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
def search_path(node, path):
if not node:
return
path += str(node.val)
if not node.left and not node.right:
paths.append(path)
else:
path += "->"
search_path(node.left, path)
search_path(node.right, path)
paths = []
if root:
search_path(root, path="")
return paths
路径总和
LeetCode112
https://leetcode.cn/problems/path-sum/
思路分析
思路转换
是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum?
转换为一个小问题
假设从根节点到当前节点的值之和为nodesSum,是否存在从当前节点的子节点到叶子节点的路径,满足路径和为targetSum-nodesSum
若当前节点为叶子结点,判断 targeSum 是否等于 nodesSum
若当前节点不是叶子节点,递归继续判断
代码实现
# 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 hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root:
return False
elif not root.left and not root.right:6
return root.val == targetSum
else:
return self.hasPathSum(root.left, targetSum-root.val) or self.hasPathSum(root.right, targetSum-root.val)
翻转的妙用
LeetCode226
https://leetcode.cn/problems/invert-binary-tree/
思路分析
想要翻转树,就是把每一个节点的左右孩子交换一下。关键在于遍历顺序,前中后序应该选择哪一种遍历顺序。遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
从根节点开始,递归地对树进行遍历,并从叶子节点先开始翻转。如果当前遍历到的节点root的左右两颗子树都已经翻转,那么我们只需要交换两颗子树的位置,即可完成以root为根节点的整颗子树的翻转。
这道题使用前序遍历和后序遍历都可以,主要区别是前序是先处理当前节点再处理子节点,是自顶向下;后序是先处理子节点最后处理自己,是自下而上的。
本题还可以使用层次遍历实现,核心思想是元素出队时,其左右两个孩子不是直接入队,而是先反转再放进去。
代码实现
方法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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
# 方法1:前序遍历
if root:
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
方法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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
# 方法2:后序遍历
if root:
self.invertTree(root.left)
self.invertTree(root.right)
root.left, root.right = root.right, root.left
return root
方法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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
# 方法3:层次遍历
if not root:
return root
queue = collections.deque([root])
while queue:
node = queue.pop()
node.left, node.right = node.right, node.left
if node.left:
queue.appendleft(node.left)
if node.right:
queue.appendleft(node.right)
return root