五、数据结构之"链表" 相关算法

135 阅读6分钟

1. 链表是什么?

  • 多个元素组成的链表
  • 元素存储不连续,用next指针连在一起

image.png

1-1 数组 VS 链表

数组: 增删非首尾元素时往往需要移动元素

链表: 增删非首尾元素,不需要移动元素,只需要更改next的指向即可

1-2 JS中的链表

  1. js中没有链表
  2. 可以用Object模拟链表
const a = { value: 'a' } 
const b = { value: 'b' } 
const c = { value: 'c' } 
const d = { value: 'd' } 

a.next = b;
b.next = c;
c.next = d;

// 遍历链表
let p = a;
while (p) {
    console.log(p.value);
    p = p.next;
}

// 插入
const e = { value: 'e' }
c.next = e;
e.next = d;

// 删除
c.next = d;

2. 删除链表中的节点

2-1 题目

  1. 有一个单链表的 head,我们想删除它其中的一个节点 node
  2. 给你一个需要删除的节点 node 。你将 无法访问 第一个节点  head
  3. 链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。
  4. 删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
  • 给定节点的值不应该存在于链表中。
  • 链表中的节点数应该减少 1。
  • node 前面的所有值顺序相同。
  • node 后面的所有值顺序相同。

image.png

题目来源: 力扣-删除链表中的节点

2-2 解题思路

  1. 如果是常规的链表,找到被删除节点的上个节点,把上个节点的next指针指向被删除节点的下个节点
  2. 因为我们无法直接获取被删除节点的上个节点,因为链表中的节点只会指向下个节点
  3. 将被删除的节点转移到下个节点,删除下个节点之前把下个节点的值转移到现在的节点上
  4. 将5替换为1 那么此时就是4119 再删除下个节点

2-2 题解

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    node.val = node.next.val;
    node.next = node.next.next;
};

2-3 时间空间复杂度

  1. 因为这个方法没有任何循环所以它的时间复杂度为O(1)
  2. 因为这个方法中没有数组和矩阵,所以空间复杂度也是O(1)

3. 反转链表

3-1 题目

  1. 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

image.png

题目来源: 力扣-反转链表

3-2 解题思路

  1. 反转俩个节点: 将n+1的next指针指向n (俩个节点)
  2. 反转多个节点:双指针遍历链表,重复上述操作 (多个节点)
  3. 双指针一前一后遍历链表
  4. 反转双指针

3-3 题解

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let p1 = head;
    let p2 = null;
    while (p1) {
        console.log(p1.val, p2 && p2.val);
        let temp = p1.next;
        p1.next = p2;
        p2 = p1;
        p1 = temp;
    }
    return p2;
};

3-4 时间空间复杂度

  1. 因为有个while循环事件复杂度为O(n)
  2. 算法中,临时变量是单个值,没有数组没有矩阵,空间复杂度为O(1)

4. 俩数相加

4-1 题目

  1. 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

  2. 请你将两个数相加,并以相同形式返回一个表示和的链表。

  3. 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

image.png

题目来源: 力扣-俩数相加

4-2 解题思路

  1. 模拟相加操作
  2. 因为是在链表中进行操作,需要进行遍历链表

4-3 题解

  1. 新建一个空链表
  2. 遍历被相加的俩个链表
  3. 在链表元素上模拟相加操作
  4. 将个位数追加到新链表上,将十位数留到下一位去相加
var addTwoNumbers = function(l1, l2) {
    // 创建一个新的链表
    const l3 = new ListNode(0);
    // 遍历俩个链表
    let p1 = l1; 
    let p2 = l2;
    let p3 = l3;
    let carry = 0;
    // 判断两个链表是否为空
    while (p1 || p2) {
        // 获取两个链表的值
        const v1 = p1 ? p1.val : 0;
        const v2 = p2 ? p2.val : 0;
        // 相加
        const val = v1 + v2 + carry;
        // 进位
        carry = Math.floor(val / 10);
        // 将相加的结果放入新的链表
        p3.next = new ListNode(val % 10);
        // 判断两个链表是否为空,不为空则指向下一个节点
        if (p1) p1 = p1.next;
        if (p2) p2 = p2.next;
        if (p3) p3 = p3.next;
    }
    // 判断是否有进位
    if (carry) {
        p3.next = new ListNode(carry);
    }
    // 返回新的链表
    return l3.next;
};

4-4 时间空间复杂度

  1. 时间复杂度l1 l2 俩个链表的最大值 O(n)
  2. 空间复杂度l1 l2 俩个链表的最大值 O(n)

5. 删除排序链表中重复元素

5-1 题目

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

image.png

题目来源: 力扣-删除排序链表中重复元素

5-2 解题思路

  1. 因为链表是有序的,所以重复元素一定相邻
  2. 遍历链表,如果发现当前元素和下个元素值相同,就删除下个元素值

5-3 题解

  1. 遍历链表,如果发现当前元素和下个元素值相同,就删除下个元素值
  2. 遍历结束后,返回原链表的头部
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
    // 遍历链表,删除重复的元素
    let p = head;
    while (p && p.next) {
        if (p.val === p.next.val) {
            p.next = p.next.next;  // remove duplicates 更改链表指向删除链表中的元素
        } else {
            p = p.next;
        }
    }
    return head;
};

5-4 时间空间复杂度

  1. 时间复杂度: O(n) while循环体
  2. 空间复杂度: O(1) 没有数组 矩阵 链表

6. 环形链表

6-1 题目

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

image.png

题目来源: 力扣-环形链表

6-2 解题思路

  1. 两个人在圆形操场上的起点同时起跑, 速度快的人一定会超过速度慢的人一圈.
  2. 用一快一慢俩个指针遍历链表,如果指针能够相逢,那么链表就有圈

6-3 题解

  1. 用一快一慢俩个指针遍历链表,如果指针能够相逢,就返回true
  2. 遍历结束后,还没有相逢就返回false
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    let p1 = head;
    let p2 = head;
    while (p1 && p2 && p2.next) {
        p1 = p1.next;
        p2 = p2.next.next;
        if (p2) {
            p2 = p2.next;
        }
        if (p1 === p2) {
            return true;
        }
    }
    return false
};

6-4 时间空间复杂度

  1. 时间复杂度: O(n) while循环体
  2. 空间复杂度: O(1) 没有数组 矩阵 链表 线性增长