【LeetCode】设计链表II —— JavaScript实现双向链表

·  阅读 339
【LeetCode】设计链表II —— JavaScript实现双向链表

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

上次我们使用JavaScript设计了一个单链表,这次我们设计一个双链表,为什么要设计双链表呢? 因为我在一次面试中遇到了 [快手二面] ,所以就想还是把它也写出来放博客上吧 这样就又可以水一篇小知识啦 总体来说也就是比单链表多一个prev指针

707. 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

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

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/de…

1. 定义双链表中的节点构造函数

这里多了一个指向前一个节点的指针 prev

/**
 * 链表中的节点构造函数
 * @param {*} val
 */
function ListNode(val) {
  this.val = val;
  this.next = null;
  this.prev = null;
};
复制代码

2. 初始化数据结构 MyLinkedList

与上次设计单链表的思路不同,这次我们给链表增加一个伪头结点和一个伪尾结点,这样方便我们的操作。(之前的单链表设计也可以这样)

image.png

/**
 * Initialize your data structure here.
 * 初始化数据结构
 */
var MyLinkedList = function () {
  // 初始化 链表长度
  this.length = 0;
  // 这里定义一个虚拟头结点和一个虚拟伪结点,方便后续的操作
  this.head = new ListNode(0);
  this.tail = new ListNode(0);
  // 建立初始化的联系
  this.head.next = this.tail;
  this.tail.prev = this.head;
};
复制代码

 3. get

因为我们设计了一个伪头结点和一个伪尾结点,所以我们在get一个元素的时候,可以先做一个判断,看这个index是头部近还是距离尾部近,这样就可以提高查找的效率

image.png

/**
 * Get the value of the index-th node in the linked list. If the index is invalid, return -1.
 * 获取链表中第 index 个节点的值。如果索引无效,则返回-1
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function (index) {
  if (index < 0 || index >= this.length) {
    return -1;
  }


  let current = this.head;
  // 如果距离头结点近,就从头结点开始遍历
  if(index + 1 < this.length - index){
    for (let i = 0; i < index + 1; i++) {
      current = current.next;
    }
  }else{
    // 如果距离尾结点近,就从尾结点开始遍历
    current = this.tail;
    for(let i = 0; i < this.length - index; i++) {
      current = current.prev;
    }
  }

  return current ? current.val : -1;
};
复制代码

因为有了伪头结点 和 伪尾结点,我们在插入数据的三种方法就可以统一起来

  • 找到要插入节点的前驱节点prevNode和后继节点nextNode。如果要在头部插入节点,则它的前驱结点是伪头。如果要在尾部插入节点,则它的后继节点是伪尾。
  • 通过改变前驱结点和后继节点的链接关系添加元素。

image.png

向双链表中添加一个元素的统一步骤

addNode.prev = prevNode;
addNode.next = nextNode;
prevNode.next = addNode;
nextNode.prev = addNode;
复制代码

4. addAtHead

/**
 * Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
 * 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function (val) {
  // 新建一个节点,将值val传递进去
  let addNode = new ListNode(val);
  
  // 找到前驱结点和后继结点
  let prevNode = this.head;
  let nextNode = this.head.next;
  
  // 添加节点操作
  addNode.prev = prevNode;
  addNode.next = nextNode;
  prevNode.next = addNode;
  nextNode.prev = addNode;
  
  // 链表长度增加
  this.length++;
};

复制代码

5. addAtTail

/**
 * Append a node of value val to the last element of the linked list.
 * 将值为 val 的节点追加到链表的最后一个元素
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function (val) {
  // 新建尾结点
  let addNode = new ListNode(val);

  // 找到前驱结点和后继结点
  let prevNode = this.tail.prev;
  let nextNode = this.tail;
  
  // 添加节点操作
  addNode.prev = prevNode;
  addNode.next = nextNode;
  prevNode.next = addNode;
  nextNode.prev = addNode;

  this.length++;
};

复制代码

6. addAtIndex

/**
 * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
 * 在链表中的第 index 个节点之前添加值为 val  的节点。
 * 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
 * 如果 index 大于链表长度,则不会插入节点。
 * 如果index小于0,则在头部插入节点。
 * @param {number} index
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function (index, val) {
  if (index <= 0) return this.addAtHead(val);
  if (index === this.length) return this.addAtTail(val);
  if (index > this.length) return;

  let addNode = new ListNode(val);


  // 找到前驱结点和后继结点
  let prevNode = null;
  let nextNode = null;
  
  if(index < this.length - index){
    prevNode = this.head;
    for(let i = 0; i < index; i++){
      prevNode = prevNode.next;
    }
    nextNode = prevNode.next;
  }else{
    nextNode = this.tail;
    for(let i = 0; i < this.length - index; i++){
      nextNode = nextNode.prev
    }
    prevNode = nextNode.prev
  }

  // 添加节点操作
  addNode.prev = prevNode;
  addNode.next = nextNode;
  prevNode.next = addNode;
  nextNode.prev = addNode;

  this.length++;
};
复制代码

7. deleteAtIndex

  • 找到要删除节点的前驱结点prevNode和后继节点nextNode
  • 通过改变前驱结点和后继节点的链接关系删除元素。

image.png

关键步骤

prevNode.next = nextNode;
nextNode.prev = prevNode;
复制代码
/**
 * Delete the index-th node in the linked list, if the index is valid.
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function (index) {
  if (index < 0 || index >= this.length) return;
  
  // 找到前驱结点和后继结点
  let prevNode = null;
  let nextNode = null;
  
  if(index < this.length - index) {
    prevNode = this.head;
    for(let i = 0; i < index; i++){
      prevNode = prevNode.next;
    }
    nextNode = prevNode.next.next;
  }else{
    nextNode = this.tail;
    for(let i = 0; i < this.length - index - 1; i++){
      nextNode = nextNode.prev;
    }
    prevNode = nextNode.prev.prev;
  }
  
  // 删除操作
  prevNode.next = nextNode;
  nextNode.prev = prevNode;

  this.length--;
};
复制代码

运行结果

image.png

class 版本

class ListNode {
  constructor(val) {
    this.val = val;
    this.next = null;
    this.prev = null;
  }
}

class MyLinkedList {
  constructor() {
    this.length = 0;

    this.head = new ListNode(0);
    this.tail = new ListNode(0);

    this.head.next = this.tail;
    this.tail.prev = this.head;
  }
  get(index) {
    if (index < 0 || index >= this.length) {
      return -1;
    }

    let current = this.head;
    // 如果距离头结点近,就从头结点开始遍历
    if (index + 1 < this.length - index) {
      for (let i = 0; i < index + 1; i++) {
        current = current.next;
      }
    } else {
      // 如果距离尾结点近,就从尾结点开始遍历
      current = this.tail;
      for (let i = 0; i < this.length - index; i++) {
        current = current.prev;
      }
    }

    return current ? current.val : -1;
  }
  addAtHead(val) {
    // 新建一个节点,将值val传递进去
    let addNode = new ListNode(val);

    // 找到前驱结点和后继结点
    let prevNode = this.head;
    let nextNode = this.head.next;

    // 添加节点操作
    addNode.prev = prevNode;
    addNode.next = nextNode;
    prevNode.next = addNode;
    nextNode.prev = addNode;

    // 链表长度增加
    this.length++;
  }
  addAtTail(val) {
    // 新建尾结点
    let addNode = new ListNode(val);

    // 找到前驱结点和后继结点
    let prevNode = this.tail.prev;
    let nextNode = this.tail;

    // 添加节点操作
    addNode.prev = prevNode;
    addNode.next = nextNode;
    prevNode.next = addNode;
    nextNode.prev = addNode;

    this.length++;
  }
  addAtIndex(index, val) {
    if (index <= 0) return this.addAtHead(val);
    if (index === this.length) return this.addAtTail(val);
    if (index > this.length) return;

    let addNode = new ListNode(val);

    // 找到前驱结点和后继结点
    let prevNode = null;
    let nextNode = null;

    if (index < this.length - index) {
      prevNode = this.head;
      for (let i = 0; i < index; i++) {
        prevNode = prevNode.next;
      }
      nextNode = prevNode.next;
    } else {
      nextNode = this.tail;
      for (let i = 0; i < this.length - index; i++) {
        nextNode = nextNode.prev;
      }
      prevNode = nextNode.prev;
    }

    // 添加节点操作
    addNode.prev = prevNode;
    addNode.next = nextNode;
    prevNode.next = addNode;
    nextNode.prev = addNode;

    this.length++;
  }
  deleteAtIndex(index) {
    if (index < 0 || index >= this.length) return;

    // 找到前驱结点和后继结点
    let prevNode = null;
    let nextNode = null;

    if (index < this.length - index) {
      prevNode = this.head;
      for (let i = 0; i < index; i++) {
        prevNode = prevNode.next;
      }
      nextNode = prevNode.next.next;
    } else {
      nextNode = this.tail;
      for (let i = 0; i < this.length - index - 1; i++) {
        nextNode = nextNode.prev;
      }
      prevNode = nextNode.prev.prev;
    }

    // 删除操作
    prevNode.next = nextNode;
    nextNode.prev = prevNode;

    this.length--;
  }
}

复制代码
分类:
前端
分类:
前端