116. 填充每个节点的下一个右侧节点指针

116. 填充每个节点的下一个右侧节点指针

问题描述

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

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可以借助队列,逐层入队,逐层出队。

具体实现步骤
  1. 新建队列queue,将根结点root入队
  2. 当队列中仍有元素时,循环遍历,并计算当前队列中的元素数量(即每层得结点数),用于下级循环(遍历本层结点和下层子结点)。
  3. 循环每层元素
  4. 每次循环中,弹出当前队列中第一个结点,只要不是每层最后一个结点,就出队一个新元素,并且赋给前一个结点的next
  5. 然后将当前结点的左右子树分别入队
  6. 重复4-5步骤,直至当前层结点全部便处理完毕
  7. 重复3-5步骤,直至队列中无元素
  8. 返回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,将每层的结点串联起来,此时有两种连接情况

  1. 同一个父结点的左右子结点:左结点的next直接指向右结点
    node.left.next = node.right
    
  2. 不同父结点的两个相邻子结点
    node.right.next = node.next.left
    

因此,可以使用上一层各个父结点的next关联,来连接其各个子结点。第一层只有一个结点,可直接连接第二层结点;第二层结点连接完毕后,可遍历第二层,连接第三层结点,依次循环。便利当前已连接完毕的层,来连接下一层结点

具体实现

有两种方式:
1.利用链表,引入一个头节点,将每层连接为一个链表,循环遍历链表即可

  1. 新建一个结点curr,用于遍历当前层,指向root,
  2. 当curr不为空,循环遍历
  3. 新建一个结点dummy,作为下层链表的头节点,新建一个节点pre,用于遍历当前层的下一层,指向dummy
  4. 当前结点不为空或者当前结点的左子树不为空,循环遍历
  5. 将pre节点指向当前结点的左子树
  6. 将pre的next指向当前层的右子树
  7. 将pre指向pre的next
  8. 将curr指向curr.next
  9. 重复5-7步骤,直至下一层循环结束
  10. curr指向下层头结点的dummy.next
  11. 重复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.不借助任何数据机构,直接利用树本身来实现,其方式基本与链表类似

  1. 解决root为空的情况
  2. 连接root左右子树(此步骤可以放在循环中)
  3. 将curr结点指向root.left,curr结点用于标记当前层的第一个结点
  4. 当curr不为空,则循环当前层
  5. 新建一个结点指向temp用于循环当前层结点
  6. 如果当前结点temp不为空,则循环遍历当前层
  7. 如果当前结点temp有左右子树,则将temp.left.next指向temp.right
  8. 如果当前结点temp有next,则将temp.right.next指向temp.next.left
  9. temp指向temp.next
  10. 重复步骤7-9,直至temp为空
  11. 将curr指向curr.left,来循环下一层
  12. 重复步骤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完结~撒花在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值