每日一题:设计链表

141 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

在js中没有自带的链表结构,我们需要模拟出链表结构和其操作方法

一、题目概述:

leetcode第707题设计链表

  1. 设计链表的实现。可以选择使用单链表或双链表。

    • 单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。
    • 如果是双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
  2. 同时实现这些链表的功能:

    • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1
    • addAtHead(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

二、思路分析:

链表的设计考察了链表的特点、函数封装的能力。

这里我们先实现一个单向链表

  1. 对于节点,有两个属性:val和next
  2. 查询get(index)时,我们要从头节点i=0开始循环查找,当循环到index时
  3. 头部插入节点:我们需要新建一个节点做新的头节点,然后将这个节点的next指向之前的头节点。
  4. 尾部插入节点:新建一个节点,将原先的尾节点的next指向新建节点
  5. 指定位置添加:对index进行循环,定义一个当前节点变量,每次循环链表时,当前节点都后移一位,当到指定的index时,新建一个节点,当前节点的前一个节点next指向新节点,新节点的next指向当前index位置的节点(原先两个节点断开,中间插入一个)
  6. 指定位置删除:对链表进行循环,当节点位置为index时,断开index-1和index的连接,将index-1指向index+1,就删除了index的节点
  7. 在这些增删操作中要注意length变量的维护

三、AC 代码:

// 定义节点
class Node {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
};

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

/** 
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function (index) {
  if (this.length && index>=0 && index < this.length) {
    let i = 0, cur = this.head;
    while (i < index) {
      cur = cur.next
      i++
    }
    return cur.val;
  }
  return -1;
};

/** 
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function (val) {
  const node = new Node(val)
  node.next = this.head
  this.head = node
  this.length += 1;
};

/** 
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function (val) {
  const node = new Node(val);
  let cur = this.head
  if (this.head==null) {
      this.head = node
  } else {
    while (cur.next) {
        cur = cur.next
    }
    cur.next = node
  }
  this.length += 1;
};

/** 
* @param {number} index 
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function (index, val) {
  if (index > this.length) {
    return
  }
  switch (index) {
    case 0:
      this.addAtHead(val);
      break;
    case this.length:
      this.addAtTail(val);
      break;
    default:
      let i = 0, cur = this.head, prev;
      const node = new Node(val);
      while (i < index) {
        prev = cur
        cur = cur.next
        i++
      }
      // i=index时跳出循环,此时对应节点cur
      prev.next = node
      node.next = cur;
      this.length += 1;
      break;
  }
};

/** 
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function (index) {
  if (this.length === 0) {
    return
  }
  if (index > -1 && index < this.length) {
    let i = 0, cur = this.head, prev;
    if (index === 0) {
      this.head = this.head.next
    } else {
      while (i < index) {
        prev = cur
        cur = cur.next
        i++
      }
      // 当i=index时跳出循环,对应当前节点为cur
      prev.next = cur.next
    }
    this.length -= 1
  }
};

四、总结:

前端js对象多层级套用,对象一个属性指向另一个对象地址,一个一个的连续指向就是链表。

每种数据结构都有自己的特点,增删改查思路各不一样,应用场景也不一样。

树、链表、图这些复杂的数据结构都是指向性的数据结构。

最核心的两种数据结构:数组&链表。其他数据结构基本上可以通过这两种数据结构模拟出来。

链表特点:

  1. 新增和删除操作比数组有优势
  2. 查询没有数组快,对随机访问不友好