问题描述
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
示例:
输入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1}
输出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"7"},"val":1}
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。
提示:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
解法一
此题目,最容易想到的解法就是利用宽度优先搜索(BFS)
,逐行遍历二叉树,并且前后串联起来。BFS
可以借助队列,逐层入队,逐层出队。
具体实现步骤
- 新建队列queue,将根结点root入队
- 当队列中仍有元素时,循环遍历,并计算当前队列中的元素数量(即每层得结点数),用于下级循环(遍历本层结点和下层子结点)。
- 循环每层元素
- 每次循环中,弹出当前队列中第一个结点,只要不是每层最后一个结点,就出队一个新元素,并且赋给前一个结点的next
- 然后将当前结点的左右子树分别入队
- 重复4-5步骤,直至当前层结点全部便处理完毕
- 重复3-5步骤,直至队列中无元素
- 返回root
代码如下
public NodeNext connect(NodeNext root) {
if (root == null) {
return root;
}
// 新建队列,用来给二叉树分层连接
Queue<NodeNext> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size();
//循环队列中的所有元素,所有元素即为一层
for (int i = 0; i < size; i++) {
// 当前层的第一个
NodeNext node = queue.poll();
// 连接前后节点
if (i < size - 1) {
node.next = queue.peek();
}
// 将当前节点左右子树放入队列,当当前层全部处理完毕后,下一层子树也放入队列种
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
return root;
}
效率如下:
由于对于队列的操作,每个结点都入队出队操作,导致整体效率不是很高。
复杂度分析
时间复杂度:O(N),N为结点个数。每个结点只会被访问一次。
空间复杂度:O(N)。其复杂度取决于每个层级最大元素数量,完美二叉树最后一层包含N/2个结点。
解法二
使用已建立的next,将每层的结点串联起来,此时有两种连接情况
- 同一个父结点的左右子结点:左结点的next直接指向右结点
node.left.next = node.right
- 不同父结点的两个相邻子结点
node.right.next = node.next.left
因此,可以使用上一层各个父结点的next关联,来连接其各个子结点。第一层只有一个结点,可直接连接第二层结点;第二层结点连接完毕后,可遍历第二层,连接第三层结点,依次循环。便利当前已连接完毕的层,来连接下一层结点。
具体实现
有两种方式:
1.利用链表,引入一个头节点,将每层连接为一个链表,循环遍历链表即可
- 新建一个结点curr,用于遍历当前层,指向root,
- 当curr不为空,循环遍历
- 新建一个结点dummy,作为下层链表的头节点,新建一个节点pre,用于遍历当前层的下一层,指向dummy
- 当前结点不为空或者当前结点的左子树不为空,循环遍历
- 将pre节点指向当前结点的左子树
- 将pre的next指向当前层的右子树
- 将pre指向pre的next
- 将curr指向curr.next
- 重复5-7步骤,直至下一层循环结束
- curr指向下层头结点的dummy.next
- 重复3-10步骤,直到遍历结束
代码实现:
public Node connect(Node root) {
if (root == null)
return root;
//cur我们可以把它看做是每一层的链表
Node cur = root;
while (cur != null) {
//遍历当前层的时候,为了方便操作在下一
//层前面添加一个哑结点(注意这里是访问
//当前层的节点,然后把下一层的节点串起来)
Node dummy = new Node(0);
//pre表示下一层节点的前一个节点
Node pre = dummy;
//然后开始遍历当前层的链表
//因为是完美二叉树,如果有左子节点就一定有右子节点
while (cur != null && cur.left != null) {
//让pre节点的next指向当前节点的左子节点,也就是把它串起来
pre.next = cur.left;
//然后再更新pre
pre = pre.next;
//pre节点的next指向当前节点的右子节点,
pre.next = cur.right;
pre = pre.next;
//继续访问这一行的下一个节点
cur = cur.next;
}
//把下一层串联成一个链表之后,让他赋值给cur,
//后续继续循环,直到cur为空为止
cur = dummy.next;
}
return root;
}
效率如下:
2.不借助任何数据机构,直接利用树本身来实现,其方式基本与链表类似
- 解决root为空的情况
- 连接root左右子树(此步骤可以放在循环中)
- 将curr结点指向root.left,curr结点用于标记当前层的第一个结点
- 当curr不为空,则循环当前层
- 新建一个结点指向temp用于循环当前层结点
- 如果当前结点temp不为空,则循环遍历当前层
- 如果当前结点temp有左右子树,则将temp.left.next指向temp.right
- 如果当前结点temp有next,则将temp.right.next指向temp.next.left
- temp指向temp.next
- 重复步骤7-9,直至temp为空
- 将curr指向curr.left,来循环下一层
- 重复步骤5-11,直至curr为空
代码实现:
class Solution {
public Node connect(Node root) {
if (root == null || (root.left == null && root.right == null)) {
return root;
}
root.left.next = root.right;
Node curr = root.left;
while (curr != null) {
Node temp = curr;
while (temp!= null) {
if (temp.left != null && temp.right != null) {
temp.left.next = temp.right;
if (temp.next != null) {
temp.right.next = temp.next.left;
}
}
temp = temp.next;
}
curr = curr.left;
}
return root;
}
}
效率如下:
两种方式效率基本一致,只是是否借助头结点的区别。
解法三
递归方式,即解法2的递归方式
代码如下:
public Node connect(Node root) {
dfs(root, null);
return root;
}
private void dfs(Node curr, Node next) {
if (curr == null)
return;
curr.next = next;
dfs(curr.left, curr.right);
dfs(curr.right, curr.next == null ? null : curr.next.left);
}
效率如下:
PS:此篇中解法二的链表法和解法三的递归法我没有写代码尝试,直接转载的LeetCode上大神总结,附上链接:BFS和递归共4种方式解决(最后3种击败了100%的用户)~
116完结~撒花