JavaScript数据结构与算法(链表)

6,261 阅读4分钟

去年4,5月份得时候看过Vue得源码。没记错的话其中的Cache类应该就是用链表实现的. 虽然用得不多但是作为数据结构的的重要组成部分,掌握它也是非常有必要的,下面主要以单链表进行说明.

开始

为了便于编写和测试,首先设置几个标准类

// 链表节点
class ListNode{
    constructor(val){
        this.val = val;
        this.next = null;
    }
}
// 将数组转化为链表
class chainList(arr){
    let head = new ListNode(arr[0]);
    let tail = head;
    for (let i = 1; i < arr.length; i++) {
        let node = new ListNode(arr[i]);
        tail.next = node;
        tail = node;
    }
    return head;
}
// 构建一个栈
const createStack = () => {
    class Stack{
        constructor(){
            this.top = 0;
            this.stores = [];
        }
        push(item){
            this.top++;
            return this.stores.push(item)
        }
        pop(){
            this.top--
            return this.stores.pop()
        }
        peer(){
            return this.stores[this.stores.length-1]
        }
        isEmpty(){
            return this.top == 0;
        }
    }
    return new Stack();
}

题目

要熟练的运用链表,最好还是多做题

翻转一个链表I

lintCode

样例
给出一个链表1->2->3->null,这个翻转后的链表为3->2->1->null
挑战
在原地一次翻转完成

利用栈的先进后出来倒序

const reverse = function (head) {
    if (!head){
        return null;
    }
    let node = head;
    let stack = createStack();
    while (node != null) {
        stack.push(node);
        node = node.next;
    }
    let newHead = null, tail = null;
    while (!stack.isEmpty()) {
        let node = stack.pop();
        if (!newHead) {
            newHead = tail = node;
        }
        tail.next = node;
        tail = node;
    }
    tail.next = null;
    return newHead
}

翻转一个链表II

lintCode

样例
给出链表1->2->3->4->5->null, m = 2 和n = 4,返回1->4->3->2->5->null
挑战
在原地一次翻转完成

首先找到要倒序的起始节点和结束节点,然后利用栈的先进后出来倒序,最后将倒序之后的链接插入到原链表

const reverseBetween = function (head, m, n) {
    let node = head, stack = createStack();
    for (let i = 1; i < m-1; i++) {
        node = node.next;
    }
    let tail = null;
    if (m != 1) {
        tail = node;
        node = node.next;
    }
    for (let i = m; i <= n; i++) {
        stack.push(node)
        node = node.next;
    }
    while (!stack.isEmpty()) {
        let node = stack.pop();
        if (!tail) {
            tail = node;
            head=node
        }
        tail.next = node;
        tail = node;
    }
    tail.next = node;
    return head
}

链表求和 II

lintCode

样例
给出 6->1->7 + 2->9->5。即,617 + 295。

返回 9->1->2。即,912 。

注意,这道题不能这么做,617+295=912然后把912变成链表,因为个问题当链表足够长时JS得到得整型值会溢出。正确得解题是思路是对链表进行反转,然后对应得节点进行求和以此来得到最终得链表

const addLists2 = function (l1, l2) {
     let newL1 = reverse(l1);
    let newL2 = reverse(l2);
    let flag = 0, stack = createStack();

    while (newL1 && newL2) {
        let val1 = newL1.val;
        let val2 = newL2.val;
        let sum  = val1 + val2 + flag;
        if (sum >=10) {
            flag = 1;
            sum = sum-10
        } else {
            flag = 0;
        }
        stack.push(sum);
        newL1 = newL1.next;
        newL2 = newL2.next;
    }
    let reserve = newL1 ? newL1:newL2;
    if (reserve){
        reserve.val = reserve.val +flag;
    }else {
        if (flag) {
            stack.push(flag)
        }
    }
    while (reserve) {
        stack.push(reserve.val)
        reserve = reserve.next;
    }
    let head = null, tail = null;
    while (!stack.isEmpty()) {
        let node = new ListNode(stack.pop());
        if (!head) {
            tail = head = node;
        } else {
            tail.next = node;
            tail = node;
        }
    }
    return head;
}

合并两个排序链表

lintCode

样例
给出 1->3->8->11->15->null,2->null, 返回 1->2->3->8->11->15->null。

这道题很简单,通过while循环比较二个链接得节点值 较小得放到新得链表中并往后移动一位,知道其中一个链表为空

const mergeTwoLists = function (l1, l2) {
    let l = null, tail = null; 
    if (!l1) {
        return l2
    } else if (!l2) {
        return l1
    }
    while (l1 != null && l2 != null) {
        let node = null;
        if (l1.val < l2.val) {
            node = l1;
            l1 = l1.next;
        } else {
            node = l2;
            l2 = l2.next;
        }
        if (!l) {
            l = node;
            tail = node;
        } else {
            tail.next = node;
            tail = node;
        }
    }
    let remain = l1 ? l1 :l2;
    while (remain) {
        tail.next = remain;
        tail = remain;
        remain = remain.next
    }        
    return l
}

删除链表中的元素

lintCode


样例
给出链表 1->2->3->3->4->5->3, 和 val = 3, 你需要返回删除3之后的链表:1->2->4->5

遇到需要删除得值时,讲前面一个节点得next设为当前节点得next就行了


const removeElements = function (head, val) {
    let node = head, preNode = head;
    while (node) {
        if (node.val == val) {
            if (node == head && head) {
                head = head.next;
                node = head
                continue
            } else {
                preNode.next = node.next;
                 node = node.next;
                continue
            }
        }
        preNode = node;
        node = node.next;
    }
    return head
}

复制带随机指针的链表

lintCode


描述
给出一个链表,每个节点包含一个额外增加的随机指针可以指向链表中的任何节点或空的节点。

返回一个深拷贝的链表。

这个题主要是随机指针得处理,可以通过hash进行映射找到随机指针对应得节点

const copyRandomList = function (l1) {
   let l2 = null, tail2 = null, node1 = l1; hash = new Map();
   // 忽略random对l1进行复制
   while (node1) {
       if (!l2) {
           l2 = tail2 = node1
       } else {
           tail2.next = node1;
           tail2 = node1;
       }
       hash.set(node1, tail2);
       node1 = node1.next
   }
   while (l2 && l1) {
       l2.random = hash.get(l1.random) 
       l2 = l2.next;
   }
   return l2
}