携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情
大家好呀,我是帅蛋。
今天解决合并二叉树,将两棵二叉树根据给出的规则合并成一棵二叉树。
咱们话不多说,直接开整。
LeetCode 617:合并二叉树
题意
给定两棵二叉树 root1 和 root2,合并成一棵新的二叉树,合并规则为:
如果两个节点重叠,将两个节点的值相加作为合并后节点的新值,否则,不为 null 的节点将直接作为新二叉树的节点。
示例
输入:root1 = [1,3,2,5],root2 = [2,1,3,null,4,null,7] 输出:[3,4,5,5,4,null,7]
提示
合并过程必须从两个树的根节点开始。
- 两棵树中的节点数目在范围 [0,2000] 内
- -10^4 <= Node.val <= 10^4
题目解析
难度简单,二叉树遍历解法经典题目。
合并二叉树这道题,具体做法其实都已经在描述里了,其实就是分两种情况讨论:
- 如果两棵树对应位置上都有节点,则新节点的值为两个节点的值相加。
- 如果两棵树对应位置上只有一个节点有值,则新节点的值就为该节点的值。
其实就是遍历两棵二叉树,在这个过程中遵守上面的两种情况即可。
我用颜色稍微标记一下,可能看的更清楚一些:
递归法
递归法就是老套路,根据【递归算法】文章中讲的,实现递归,需要两步:
- 找出重复的子问题(递推公式)。
- 终止条件。
(1) 找出重复的子问题。
这个在上面看图的时候其实已经找出来了。
对于每一层来说,就是重新创建一个节点 root 存储 root1.val + root2.val。
之后 root 的左子树就是合并 root1 左子树和 root2 左子树之后的左子树,root 的右子树就是合并 root1 右子树和 root2 右子树之后的右子树。
# 如果都存在节点,创建一个新的节点存储合并后的值
root = TreeNode(root1.val + root2.val)
# 递归合并左子树
root.left = self.mergeTrees(root1.left, root2.left)
# 递归合并右子树
root.right = self.mergeTrees(root1.right, root2.right)
你看这种先根节点再左子树最后右子树的方式是不是很眼熟?这就是我们学的前序遍历。
当然像中序遍历或者后序遍历的方式都是可以的,我在这就不多赘述,大家可以自己写着试试。
(2) 确定终止条件。
对于中止条件的话,在本题中就是:
对于遍历的两棵二叉树 root1 和 root2,如果 root1 的节点为空,那合并之后就应该是 root2,同样如果 root2 的节点为空,那合并之后就应该是 root1。
// 如果 root1 为空,则合并之后节点为 root2
if(root1 == null){
return root2;
}
// 如果 root2 为空,则合并之后节点为 root1
if(root2 == null){
return root1;
}
递归二步曲确定完了,这道题的代码其实也就做出来了。
Python 代码实现
# 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: TreeNode, root2: TreeNode) -> TreeNode:
# 如果 root1 为空,则合并之后节点为 root2
if root1 == None:
return root2
# 如果 root2 为空,则合并之后节点为 root1
if root2 == None:
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
Java 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
// 如果 root1 为空,则合并之后节点为 root2
if(root1 == null){
return root2;
}
// 如果 root2 为空,则合并之后节点为 root1
if(root2 == null){
return root1;
}
// 如果都存在节点,创建一个新的节点存储合并后的值
TreeNode root = new TreeNode(root1.val + root2.val);
// 递归合并左子树
root.left = mergeTrees(root1.left, root2.left);
// 递归合并右子树
root.right = mergeTrees(root1.right, root2.right);
return root;
}
}
假设 root1 的节点数为 n,root2 的节点数为 m,对于两棵二叉树而言,只有对应的节点都不为空时才会进行合并操作,所以合并的时候一定是遍历完了节点数相对较少的二叉树,所以总的时间复杂度为* *O(min(n,m))**。
此外在递归过程中调用了额外的栈空间,栈的大小取决于二叉树的高度,二叉树 root1 最坏情况下的高度为 n,二叉树 root2 最坏情况下的高度为 m,所以空间复杂度为 O(min(n,m)) 。
非递归法(迭代)
在递归法中,我们在找重复子问题的时候,对于每一层来说,都是看 root1 的左子树和 root2 的左子树,以及 root1 的右子树和 root2 的右子树。
这种一层层的看下去有点类似层次遍历。
我在【层次遍历】文章中说过,非递归版的层次遍历用的则是队列,这是由于层次遍历的属性非常契合队列的特点。
在本题中,具体的做法就是维护两个队列,队列 queueMerge 存储合并后的树节点,queue 则是 root1 和 root2 的节点,具体做法我们来图解。
比如对于下图,左边是 root1,右边是 root2:
首先我们先创建一个新的根节点 root,其值为 root1.val + root2.val,同时初始化两个队列,将 root 入 queueMerge 队列,将 root1 和 root2 入 queue 队列。
# 如果都存在节点,创建一个新的节点存储合并后的值
root = TreeNode(root1.val + root2.val)
# 初始化队列
queueMerge = [root]
queue = [root1, root2]
此时队列不为空,将 queueMerge 队首元素出队列,此时 node = 3,同时将 queue 两个队首元素出队列,此时 node1 = 1,node2 = 2。
对于 node1 和 node2,都存在左孩子,根据题目的描述,node 的左孩子为 node1 左孩子的值 + node2 左孩子的值。
同时将 node 的左孩子入 queueMerge 队列,将 node1 的左孩子和 node2 的左孩子入 queue 队列。
# 从队列中取出当前节点
node = queueMerge.pop(0)
node1 = queue.pop(0)
node2 = queue.pop(0)
if node1.left or node2.left:
# 若两棵树的左孩子都存在
if node1.left and node2.left:
leftMerge = TreeNode(node1.left.val + node2.left.val)
node.left = leftMerge
queueMerge.append(leftMerge)
queue.append(node1.left)
queue.append(node2.left)
同样,对于 node1 和 node2 来说,都存在右孩子, 根据题目的描述,node 的右孩子为 node1 右孩子的值 + node2 右孩子的值。
同时将 node 的右孩子入 queueMerge 队列,将 node1 的右孩子和 node2 的右孩子入 queue 队列。
if node1.right or node2.right:
# 若两棵树的右孩子都在
if node1.right and node2.right:
rightMerge = TreeNode(node1.right.val + node2.right.val)
node.right = rightMerge
queueMerge.append(rightMerge)
queue.append(node1.right)
queue.append(node2.right)
队列不为空,继续出队列,root = 4,root1 = 3, root2 = 1。
此时 root1 有左孩子,root2 没有左孩子,则 root 的左孩子直接为 root1 的左孩子。
elif node1.left:
node.left = node1.left
同时此时 root1 没有右孩子,root2 有右孩子,则 root 的右孩子直接为 root2 的右孩子。
elif node2.right:
node.right = node2.right
接下来就是重复上面的操作,直至队列为空,返回最后的结果。
Python 代码实现
# 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: TreeNode, root2: TreeNode) -> TreeNode:
# 如果 root1 为空,则合并之后节点为 root2
if root1 == None:
return root2
# 如果 root2 为空,则合并之后节点为 root1
if root2 == None:
return root1
# 如果都存在节点,创建一个新的节点存储合并后的值
root = TreeNode(root1.val + root2.val)
# 初始化队列
queueMerge = [root]
queue = [root1, root2]
while queue:
# 从队列中取出当前节点
node = queueMerge.pop(0)
node1 = queue.pop(0)
node2 = queue.pop(0)
if node1.left or node2.left:
# 若两棵树的左孩子都存在
if node1.left and node2.left:
leftMerge = TreeNode(node1.left.val + node2.left.val)
node.left = leftMerge
queueMerge.append(leftMerge)
queue.append(node1.left)
queue.append(node2.left)
# 若只有一棵树存在左孩子,直接赋值
elif node1.left:
node.left = node1.left
elif node2.left:
node.left = node2.left
if node1.right or node2.right:
# 若两棵树的右孩子都在
if node1.right and node2.right:
rightMerge = TreeNode(node1.right.val + node2.right.val)
node.right = rightMerge
queueMerge.append(rightMerge)
queue.append(node1.right)
queue.append(node2.right)
# 若只有一棵树存在右孩子,直接赋值
elif node1.right:
node.right = node1.right
elif node2.right:
node.right = node2.right
return root
Java 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
// 如果 root1 为空,则合并之后节点为 root2
if(root1 == null){
return root2;
}
// 如果 root2 为空,则合并之后节点为 root1
if(root2 == null){
return root1;
}
// 如果都存在节点,创建一个新的节点存储合并后的值
TreeNode root = new TreeNode(root1.val + root2.val);
// 初始化队列
Queue<TreeNode> queueMerge = new LinkedList<TreeNode>();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queueMerge.offer(root);
queue.offer(root1);
queue.offer(root2);
while(!queue.isEmpty()){
// 从队列中取出当前节点
TreeNode node = queueMerge.poll();
TreeNode node1 = queue.poll(), node2 = queue.poll();
if(node1.left != null || node2.left != null){
// 若两棵树的左孩子都存在
if(node1.left != null && node2.left != null){
TreeNode leftMerge = new TreeNode(node1.left.val + node2.left.val);
node.left = leftMerge;
queueMerge.offer(leftMerge);
queue.offer(node1.left);
queue.offer(node2.left);
}
// 若只有一棵树存在左孩子,直接赋值
else if(node1.left != null){
node.left = node1.left;
}
else if(node2.left != null){
node.left = node2.left;
}
}
// 若两棵树的右孩子都在
if(node1.right != null || node2.right != null){
if(node1.right != null && node2.right != null){
TreeNode rightMerge = new TreeNode(node1.right.val + node2.right.val);
node.right = rightMerge;
queueMerge.offer(rightMerge);
queue.offer(node1.right);
queue.offer(node2.right);
}
// 若只有一棵树存在右孩子,直接赋值
else if(node1.right != null){
node.right = node1.right;
}
else if(node2.right != null){
node.right = node2.right;
}
}
}
return root;
}
}
同样,对于非递归法,它总的时间复杂度为O(min(n,m)),空间复杂度为 O(min(n,m)) 。
图解合并二叉树到这就结束辣,大家学会了嘛?是不是大喊一声,二叉树遍历,YYDS!
这道题我在写的时候就觉得贼神奇,递归法几行代码,非递归法五六十行,累了累了。
今天就先到这,记得做好总结,还有...本蛋的点赞~
我是帅蛋,我们下次见!