JavaScript資料結構與演算法(連結串列)

fiveoneLei發表於2019-03-02

去年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
}
複製程式碼

相關文章