#数据结构-链表

137 阅读4分钟

推荐先看看大话数据结构这本书,一点都不枯燥,写的很好.

牛客网算法直通车总结,感谢.

链表

链表基本知识

  • 线性结构, 不是连续的储存空间
  • 链表空间不能保证连续有序, 为临时分配的
  • 无法使用下标来访问元素, 只能从头结点, 一个一个往下遍历

链表的分类:

  • 按照方向分类
    • 单链表
    • 双链表
  • 按照无环分类
    • 普通链表
    • 循环链表

代码实现的关键点:

  1. 链表调整函数的返回值类型, 根据要求往往是结点类型, 调整链表可能换了头结点
  2. 处理链表问题, 辅佐画图保证逻辑清晰, 环境需要先保存下来, 防止断线的情况
  3. 边界条件要讨论严格

插入和删除的情况:

  1. 注意链表为空, 或者长度为 1 的情况
  2. 注意插入的操作的调整过程
    1. 找到插入位置的前元素和后元素
    2. 前节点的 next 指针指向插入的元素
    3. 插入元素的 next 指针指向后元素
  3. 注意删除节点的调整过程
    1. 单链表中: 找到删除节点的前元素
    2. 前节点的 next 指针指向删除节点的后继节点
  4. 在头和尾插入和删除的情况下需要考虑空节点
  5. 双链表的插入和删除和单链表类似, 但是要考虑 previous 指针的指向

链表翻转的注意事项:

  1. 当链表为空或者长度为 1 的特殊情况
  2. 单链表的翻转操作: head->now->...->null
    1. now 的 next 指针指向 head
    2. 将 now 设置为翻转部分的新的头部
    3. 在步骤 1 发生之前, 先记录下 now 节点的下一个节点是什么, 然后载进行步骤 1 和步骤 2, 前两个步骤进行完后, 在根据刚才记录 now 节点的下一个节点继续向下处理

LeetCode 24-反转链表

使用递归

  • 时间复杂度:O(n),假设 n 是列表的长度,那么时间复杂度为 O(n)
  • 空间复杂度:O(n),由于使用递归,将会使用隐式栈空间。递归深度可能会达到 n 层。
/**
* Definition for singly-linked list.
* type ListNode struct {
*     Val int
*     Next *ListNode
* }
*/
func reverseList(head *ListNode) *ListNode {
   if head == nil || head.Next == nil {
      return head
   }
   next := head.Next
   head.Next = nil
   newHead := reverseList(next)
   next.Next = head
   return newHead
}
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
       if (head == null || head.next == null)
        return head;

        ListNode next = head.next;
        head.next = null;
        ListNode newHead = reverseList(next);
        next.next = head;
        return newHead;
    }

    public ListNode reverseList2(ListNode head) {
      if (head == null || head.next == null) return head;
      ListNode p = reverseList(head.next);
      head.next.next = head;
      head.next = null;
      return p;
   }

使用迭代:

  • 时间复杂度 O(n)
  • 空间复杂符 O(1)
/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
   var prev *ListNode
   curr := head
   for curr!=nil {
       tmp := curr.Next
       curr.Next = prev
       prev = curr
       curr = tmp
   }
   return prev
}

大量链表问题可以使用额外的数据结构来简化处理, 但是最有解往往不需要额外的数据结构.

例题

一. 给定一个整数 num, 如何在节点值有序的环形链表中插住一个节点值为 num 的节点, 然后保证这个环形链表依然后续?

  1. 生成 node, 值为 num
  2. 如果链表为空, 则让 node 自己成为环形链表
  3. 如果链表不为空
    1. 让变量 p 头结点为 head 结点
    2. 变量 c 为当前结点
    3. 让 p 和 c 同步移动下去
    4. 如果遇到 p.val<=node.val 并且 c.var>=node.val 插入在 p 和 c 之间
    5. 插入 node, 后返回 head
    6. 如果转了一圈都没有发现该插入的位置, 此时 node 应该插入头结点的前面
      1. 如果 node 的值比链表中每一个值都大, 返回原来的 head
      2. 如果比每一个值都小, 则返回 node 作为 head, 因为从 node 出发的循环链表才有序

二. 给定一个链表中的节点 node, 但是不给定整个链表的头结点, 怎么在链表中删除 node? 代码实现, 并且时间复杂度为 O(1).

  1. 双链表好实现, 找到 pre 找到 next, 然后链接就行
  2. 单链表中, 要删除节点 2, 但是不知道 head, 只需要把 2 的值变成 node3 的值, 然后删除节点 3 就行了
    1. 但是当要删除的是最后一个元素的时候, 没有办法删除最后一个节点, 因为需要把前面节点的 next 指针指向 null, 但是没有办法获得前面的元素
    2. 也不能把最后节点设置为 null, 因为 null 在内存中有特殊的含义, 必须找到前一个元素 (这不是删除节点, 而是进行了值得拷贝)
    3. 总之就是, 这个节点在结构复杂的时候开销很大, 在工程上会影响外部的依赖

LeetCode 题目

实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func deleteNode(node *ListNode) {
    toDeleteNode := node.Next
    node.Val = toDeleteNode.Val
    node.Next = toDeleteNode.Next
}