Структуры данных и алгоритмы JavaScript (связанный список)

внешний интерфейс алгоритм JavaScript Vue.js

Я видел исходный код 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

При обнаружении значения, которое необходимо удалить, достаточно сказать, что предыдущий узел должен быть следующим, а текущий узел должен быть следующим.


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


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

返回一个深拷贝的链表。

Эта проблема в основном связана со случайными указателями.Вы можете найти узел, соответствующий случайному указателю, с помощью хэш-сопоставления.

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
}