javascript版数据结构之链表(附:leetCode 2、206、83)

344 阅读4分钟

1、概念

  链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

image.png

  • 特点: 多个元素组成的列表.
  • 元素存储不连续,用next指针连在一起.
  • 插入、删除数据效率高,只需要考虑相邻结点的指针改变,不需要搬移数据,时间复杂度是 O(1).
  • 随机查找效率低,需要根据指针一个结点一个结点的遍历查找,时间复杂度为O(n).
  • 与内存相比,链表的空间消耗大,因为每个结点除了要存储数据本身,还要储存上(下)结点的地址.

2、数组 vs 链表

时间复杂度数组链表
插入删除O(n)O(1)
随机访问O(1)O(n)
  • 数组: 增删非首尾元素时往往需要移动元素.
  • 链表: 增删非首尾元素,不需要移动元素,只需要更改next的指向即可.

数组缺点

  • 数组必须占用整块、连续的内存空间,如果声明数组过大,可能回导致“内存不足”。
  • 数组不够灵活,一旦需要扩容,会重新申请连续整块空间,并需要把原数组的数据全部拷贝到新申请的空间。 链表缺点
  • 内存空间消耗更大,用于储存结点指针信息。
  • 对链表进行频繁的插入、删除操作会导致频繁的内存申请、释放,容易造成内存碎片,如果是JAVA语言,还有可能会导致频繁的GC(Garbage Collection,垃圾回收)。

3、js中的链表

js中没有链表,但可以用Object模拟链表。

const a = { val: 'a' }
const b = { val: 'b' }
const c = { val: 'c' }
const d = { val: 'd' }
a.next = b // a指向b  a->b
b.next = c // b指向c  a->b->c
c.next = d // c指向d  a->b->c->d
// 遍历链表
// 声明一个指针指向a
let p = a
while (p) {
    console.log(p.val)
    p = p.next // 指针移动到下一位
}

// 插入值
// 例如: 插入一个e到 c和d之间
const e = { val: 'e' }
c.next = e
e.next = d // a->b->c->e->d

// 删除
// 例如:删除e
c.next = d // a->b->c->d

4、leetCode

leetCode 2. 两数相加

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

  • 请你将两个数相加,并以相同形式返回一个表示和的链表。
  • 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let l3 = new ListNode(0);
    // p1 p2 p3为了遍历链表(参考上面遍历链表操作)
    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;
        p3.next = new ListNode(val % 10);
        // 获取进位
        carry = Math.floor(val / 10);
        // 链表移动到下一位
        if (p1) p1 = p1.next;
        if (p2) p2 = p2.next;
        p3 = p3.next;
    }
    // 如果最后一位相加 存在进位 则将进位添加到链表末尾
    if (carry) {
        p3.next = new ListNode(carry);
    }
    return l3.next; // 因为起始值为0  所以返回的是下一位
};

leetCode 206.反转链表

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

image.png

/**
 * 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 r = null // 反转的链表
    let p = head
    // 遍历链表
    while(p) {
        // 临时变量 存储下个节点
        const temp = p.next
        // 将当前节点的下个节点指向上个节点(起始节点的上个节点为null)
        p.next = r
        r = p
        // 遍历下个节点
        p = temp
    }
    return r
};

leetCode 83. 删除排序链表中的重复元素

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。返回同样按升序排列的结果链表。

image.png

/**
 * 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;
        } else { // 不相同 则进行下个节点遍历
            p = p.next;
        }
    }
    return head
};