用js如何设计一个链表?

668 阅读3分钟

书接上回# 链表算法题分析 - js篇,上一章是链表的定义,和一些简单的链表算法题。

因为苦逼的js没有内置的LinkList。。。所以本章我们来自己动手设计一个链表,这也是LeetCode的707题,需要实现下列方法:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

示例:

MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2);   //链表变为1-> 2-> 3
linkedList.get(1);            //返回2
linkedList.deleteAtIndex(1);  //现在链表是1-> 3
linkedList.get(1);            //返回3

思考

要满足这些方法,实例函数中需要提供些什么?

因为我们get, addAtIndex, deleteAtIndex函数要判断索引是否有效,我们如果没有链表长度,每次都要循环一遍算长度,十分消耗时间,所以我们定义一个链表的长度this.size

因为我们要实现addAtHead函数,在头部增加节点,所以我们定义一个头部指针this.head

因为我们要实现addAtTail函数,在尾部增加节点,如果每次都用指针遍历指向尾部,也是浪费时间的,所以我们增加一个this.tail指针,指向链表尾部。

image.png

代码实现

var MyLinkedList = function () {
    this.size = 0;
    this.head = null;
    this.tail = null;
};

/** 
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function (index) {
    if (index >= this.size) return -1;
    let ans = this.head;
    while (index) {
        ans = ans.next;
        index--;
    }
    return ans.val;
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function (val) {
    const node = new ListNode(val, this.head);
    if (!this.size) {
        this.tail = node;
    }
    this.head = node;
    this.size += 1;
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function (val) {
    const node = new ListNode(val, null);
    if (!this.size) {
        this.head = node;
        this.tail = node;
    } else {
        this.tail.next = node;
        this.tail = this.tail.next;
    }
    this.size += 1;
};

/** 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function (index, val) {
    if (index > this.size) {
        return
    } else if (index === this.size) {
        this.addAtTail(val)
    } else if (index <= 0) {
        this.addAtHead(val)
    } else {
        index -= 1;
        let current = this.head;
        let idx = 0;
        while (idx < index) {
            current = current.next;
            idx++;
        }
        const next = current.next;
        current.next = new ListNode(val, next);
        this.size += 1;
    }
};

/** 
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function (index) {
    if (index >= this.size) return;
    if (index === 0) {
        this.head = this.head.next;
        this.size -= 1;
        return;
    }
    let current = this.head;
    while (index > 1) {
        current = current.next;
        index--;
    }
    if (current.next === this.tail) {
        this.tail = current;
    }
    current.next = current.next.next;
    this.size -= 1;
};

function ListNode(val, next) {
    this.val = val;
    this.next = next || null;
}
/**
 * Your MyLinkedList object will be instantiated and called as such:
 * var obj = new MyLinkedList()
 * var param_1 = obj.get(index)
 * obj.addAtHead(val)
 * obj.addAtTail(val)
 * obj.addAtIndex(index,val)
 * obj.deleteAtIndex(index)
 */

哨兵节点和双链表

上面的代码有哪些容易出错或优化的点??

  • addAtHead、addAtTail需要对长度为空进行判断,初始化头尾节点
  • deleteAtIndex中if (current.next === this.tail) { this.tail = current; }这段代码,需要对尾节点进行移动
  • 查询节点时,若需要操作的节点在尾部,我们也需要从头开始遍历链表

那么如何解决呢??

  • 哨兵节点,模拟一个虚拟的伪头和伪尾节点,这样头尾的操作时间复杂度都是O(1)
  • 双链表,判断index是靠近头部还是尾部节点,操作插入,删除,查询的时间复杂度是O(min(index, size-index))

image.png

var MyLinkedList = function () {
    this.size = 0;
    const dump_head = new ListNode(-1);
    const dump_tail = new ListNode(-1);
    dump_head.next = dump_tail;
    dump_tail.prev = dump_head;
    this.head = dump_head
    this.tail = dump_tail;
};

/** 
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function (index) {
    if (index >= this.size) {
        return -1;
    }
    if ((index + 1) * 2 > this.size) {
        // 靠近尾部,从尾部查找
        let idx = this.size - index;
        let current = this.tail;
        while (idx) {
            current = current.prev;
            idx--;
        }
        return current.val;
    } else {
        let idx = index + 1;
        let current = this.head;
        while (idx) {
            current = current.next;
            idx--;
        }
        return current.val;
    }
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function (val) {
    const next = this.head.next;
    const node = new ListNode(val, next, this.head);
    next.prev = node;
    this.head.next = node;
    this.size++;
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function (val) {
    const prev = this.tail.prev;
    const node = new ListNode(val, this.tail, prev);
    this.tail.prev = node;
    prev.next = node;
    this.size++;
};

/** 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function (index, val) {
    if (index > this.size) {
        return
    } else if (index === this.size) {
        this.addAtTail(val)
    } else if (index <= 0) {
        this.addAtHead(val)
    } else {
        let idx = 0, current = null;
        if ((index + 1) * 2 > this.size) {
            idx = this.size - index;
            current = this.tail;
            while (idx) {
                current = current.prev;
                idx--;
            }
        } else {
            idx = index + 1;
            current = this.head;
            while (idx) {
                current = current.next;
                idx--;
            }
        }
        const prev = current.prev;
        const node = new ListNode(val, current, prev);
        prev.next = node;
        current.prev = node;
        this.size++;
    }
};

/** 
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function (index) {
    if (index >= 0 && index < this.size) {
        let idx = 0, current = null;
        if ((index + 1) * 2 > this.size) {
            idx = this.size - index;
            current = this.tail;
            while (idx) {
                current = current.prev;
                idx--;
            }
        } else {
            idx = index + 1;
            current = this.head;
            while (idx) {
                current = current.next;
                idx--;
            }
        }
        const prev = current.prev;
        const next = current.next;
        prev.next = next;
        next.prev = prev;
        this.size--;
    }
};

function ListNode(val, next, prev) {
    this.val = val || 0;
    this.next = next || null;
    this.prev = prev || null;
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * var obj = new MyLinkedList()
 * var param_1 = obj.get(index)
 * obj.addAtHead(val)
 * obj.addAtTail(val)
 * obj.addAtIndex(index,val)
 * obj.deleteAtIndex(index)
 */

本文的代码已收录Github,仓库中包括之前的文章链接和代码收录,后续代码也会陆续更新,欢迎大家不吝赐教。