4.5 双向链表和循环链表

Kesa...大约 5 分钟algorithm

双向链表中每个节点包含指向下一节点和上一节点的指针, 可以从两个方向遍历.

循环链表的尾节点和头节点相连形成环. 因为可以从任意节点出发到达任意节点, 所以循环链表的任意节点都可以是头节点.

4.5.1 问题28: 展平多级双向链表

LCR 028. 扁平化多级双向链表open in new window

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

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

示例 1:

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:

输入的多级列表如下图所示:



扁平化后的链表如下图:

示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

  1---2---NULL
  |
  3---NULL

示例 3:

输入:head = []
输出:[]

如何表示测试用例中的多级链表?

示例 1 为例:

 1---2---3---4---5---6--NULL
         |
         7---8---9---10--NULL
             |
             11--12--NULL

序列化其中的每一级之后:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]

合并所有序列化结果,并去除末尾的 null 。

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

提示:

  • 节点数目不超过 1000
  • 1 <= Node.val <= 10^5

4.5.1.1 分析

首先理清展开的逻辑:

  1. 将子链表展开, 形成不含子链表的链表
  2. 将已展开的子链表插入回原链表中

由于子链表也可包含子链表, 所以操作是递归的.

4.5.1.2 题解

时间复杂度: O(n)O(n) , 遍历每个节点

空间复杂度: O(k)O(k), k 为子链表的层数

type Node struct {
    Val   int
    Next  *Node
    Prev  *Node
    Child *Node
}

func flatten(root *Node) *Node {
    flattenGetTail(root)
    return root
}

func flattenGetTail(head *Node) *Node {
    node := head
    var tail *Node // 展开后的尾节点

    // 遍历链表
    for node != nil {
       next := node.Next
       // 展开子链表
       if node.Child != nil {
          child := node.Child                // 子链表头节点
          childTail := flattenGetTail(child) // 递归展开子链表, 返回展开后的尾节点

          // 插入原链表
          node.Child = nil // 断开子链表
          node.Next = child
          child.Prev = node
          childTail.Next = next
          if next != nil { // 注意双向链表的指针有两个
             next.Prev = childTail
          }
          // 更新当前展开的尾节点
          tail = childTail
       } else { // 递归出口, 无子链表
          tail = node
       }
       // 更新当前节点
       node = next
    }

    return tail
}

4.5.2 问题29: 排序的循环链表

LCR 029. 循环有序列表的插入open in new window

给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal ,使这个列表仍然是循环升序的。

给定的可以是这个列表中任意一个顶点的指针,并不一定是这个列表中最小元素的指针。

如果有多个满足条件的插入位置,可以选择任意一个位置插入新的值,插入后整个列表仍然保持有序。

如果列表为空(给定的节点是 null),需要创建一个循环有序列表并返回这个节点。否则。请返回原先给定的节点。

示例 1:

img
img
输入:head = [3,4,1], insertVal = 2
输出:[3,4,1,2]
解释:在上图中,有一个包含三个元素的循环有序列表,你获得值为 3 的节点的指针,我们需要向表中插入元素 2 。新插入的节点应该在 1 和 3 之间,插入之后,整个列表如上图所示,最后返回节点 3 。

示例 2:

输入:head = [], insertVal = 1
输出:[1]
解释:列表为空(给定的节点是 null),创建一个循环有序列表并返回这个节点。

示例 3:

输入:head = [1], insertVal = 0
输出:[1,0]

提示:

  • 0 <= Number of Nodes <= 5 * 10^4
  • -10^6 <= Node.val <= 10^6
  • -10^6 <= insertVal <= 10^6

4.5.2.1 分析

找出规则

要插入一个节点 NiN_i, 那么这个节点值一定满足: Ni1NiNi+1N_{i-1} \le N_i \le N_{i+1} . 那么问题就转化成在链表中寻找满足这种条件的节点.

边界条件

  1. 空链表: 此时插入的节点就是头节点
  2. 仅有一个节点: 将插入节点与其链接即可
  3. 最大(小)值: 若插入的节点大于(小于)最大(小)值, 则插入到头节点和尾节点之间.

4.5.2.2 题解

type Node29 struct {
    Val  int
    Next *Node29
}

func insert(aNode *Node29, x int) *Node29 {
    newNode := &Node29{Val: x}
    // 边界条件
    if aNode == nil { // 空链表
       newNode.Next = newNode
       return newNode
    } else if aNode.Next == nil { // 只有一个节点
       aNode.Next = newNode
       newNode.Next = aNode
    } else {
       insertCore(aNode, newNode)
    }

    return aNode
}

func insertCore(head, node *Node29) {
    cur := head
    next := cur.Next
    biggest := cur

    // 寻找插入位置
    for !(cur.Val <= node.Val && next.Val >= node.Val) && next != head {
       cur = next
       next = cur.Next
       if cur.Val >= biggest.Val {
          biggest = cur
       }
    }

    // 插入
    if cur.Val <= node.Val && next.Val >= node.Val { // 插入位置在中间
       cur.Next = node
       node.Next = next
    } else { // 插入位置为末尾
       node.Next = biggest.Next
       biggest.Next = node
    }

}

Reference

  1. 剑指Offer(专项突破版)open in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2