Java每日一题——> 剑指 Offer II 028. 展平多级双向链表

Posted stormzhuo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java每日一题——> 剑指 Offer II 028. 展平多级双向链表相关的知识,希望对你有一定的参考价值。

这是LeetCode上的 [028,展平多级双向链表],难度为 [中等]

题目

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。

示例


题解(递归法)

思路分析

首先需要理解展平的规则,展平的规则是一个结点的子链展平之后将插入该结点和它的下一个结点之间,由于子链也有可能有子链,因此我们容易想到到用递归来实现。

在遍历每一个双向链表时(递归实现),声明两个结点node和tail,node初始为双向链表的头结点,tail初始化为null,用于存放双向链表的尾节点。

遍历过程中,如果当前结点有子链,则以此子链的头结点调用递归,继续遍历下一个双向链表,否则,继续遍历当前链表,直至遍历到尾结点为止,以此类推,当递归遍历到子链没有子链了,即为递归出口,返回最后一个子链的尾结点作为最后一次递归函数的返回值。

当每一个递归函数有返回值后,都执行如下操作

  • 让当前结点和子链断开
  • 当前结点的后置指针指向子链的头结点
  • 子链的头结点的前置指针指向当前结点
  • 子链的尾结点后置指针指向当前结点的下一个结点
  • 如果当前结点的下一个结点不为null(有子链的当前结点可能没有下一个结点),让当前结点的下一个结点的前置指针指向子链的尾结点
  • 把子链的尾结点赋值给双向链表的尾结点tail,因为有子链的当前结点可以没有下一个结点,则子链的尾结点就是双向链表的尾结点

代码实现

class Solution 
    public Node flatten(Node head) 
        flattenGetTail(head);
        return head;
    

    public Node flattenGetTail(Node head) 
        // 存放每次递归时,双向链表的头结点
        Node node = head;
        // 存放每次递归时,双向链表的尾结点
        Node tail = null;
        // 遍历每次递归的双向链表,遍历到双向链表尾结点时终止
        while (node != null) 
            // 存放当前结点的下一个结点
            Node next = node.next;
            // 若当前结点有子链
            if (node.child != null) 
                // 存放子链的头结点
                Node child = node.child;
                /* 子链的头结点作为参数,自己调自己,实现递归
                * 递归的出口为当前子链没有子链,即最后一个子链,此时返回值为子链的尾结点*/
                Node childTail = flattenGetTail(node.child);
                // 递归有返回值时执行以下操作
                // 断开子链
                node.child = null;
                // 当前结点的后缀指针指向当前结点的子链头结点
                node.next = child;
                // 当前结点的子链头结点的前缀指针指向当前结点
                child.prev = node;
                // 当前结点的子链尾结点指向当前结点的下一个结点
                childTail.next = next;
                // 若当前结点的下一个结点不为null(有子链的当前结点可能没有下一个结点)
                if (next != null) 
                    // 当前结点的下一个结点的前置指针指向当前结点的子链尾结点
                    next.prev = childTail;
                
                // 让当前结点的子链尾结点作为当前双向链表的尾结点(有子链的当前结点可能没有下一个结点)
                tail = childTail;
            // 若当前结点没有子链,则让当前结点作为双向链表的尾结点
             else 
                tail = node;
            
            // 重置当前结点,让当前结点的下一个结点作为当前结点,实现遍历
            node = next;
        
        /* 每次递归返回的双向链表的尾结点,有两种情况
        * 如果有子链的当前结点的下一个结点不为null,则尾结点就是双向链表的尾结点
        * 如果有子链的当前结点的下一个结点为null,则尾结点是子链的尾结点*/
        return tail;
    

结点类

class Node 
    public int val;
    public Node prev;
    public Node next;
    public Node child;

    public Node(int val) 
        this.val = val;
    
;

测试代码

public class Test 

    @org.junit.Test
    public void test() 
        Node head1 = new Node(1);
        Node one = new Node(2);
        Node two = new Node(3);
        Node three = new Node(4);
        head1.next = one;
        one.prev = head1;
        one.next = two;
        two.prev = one;
        two.next = three;
        three.prev = two;

        Node head2 = new Node(5);
        Node first = new Node(6);
        Node second = new Node(7);
        head2.next = first;
        first.prev = head2;
        first.next = second;
        second.prev = first;

        Node head3 = new Node(8);
        Node o = new Node(9);
        head3.next = o;
        o.prev = head3;

        one.child = head2;

        first.child = head3;

        Node head = new Solution().flatten(head1);
        while (head != null) 
            System.out.print(head.val + " ");
            head = head.next;
        
    

复杂度分析


假设多级双向链表结点数为n

时间复杂度:

需要遍历每个双向链表,故时间复杂度为O(n)

空间复杂度:

每次递归需要声明k个结点(k为常数),而递归次数是由子链个数决定的,即递归次数 = 子链个数 + 1, 加1是第一次递归(开启递归),假设子链个数为m,则空间复杂度为 k ( O(m) + 1 ) = O(m)

以上是关于Java每日一题——> 剑指 Offer II 028. 展平多级双向链表的主要内容,如果未能解决你的问题,请参考以下文章

Java每日一题——>剑指 Offer II 029. 排序的循环链表

Java每日一题——>剑指 Offer II 035. 最小时间差(三解,蛮力,排序,哈希)

Java每日一题——> 剑指 Offer II 028. 展平多级双向链表

Java每日一题——>剑指 Offer II 027. 回文链表

Java每日一题——>剑指 Offer II 030. 插入删除和随机访问都是 O 的容器

Java每日一题——>剑指 Offer II 034. 外星语言是否排序