ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

[JavaScript 刷题] 链表II,翻转链表,搜索,按值删除

2021-09-18 22:32:27  阅读:198  来源: 互联网

标签:II head curr 按值 nextElement 链表 return null


[JavaScript 刷题] 链表II,翻转链表,搜索,按值删除

以单链表的功能为主。

Node

class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

构造函数

class LinkedList {
  constructor() {
    this.head = null;
  }
}

isEmpty

判断链表是否为空,可以通过直接判断 head 是否为 null 进行实现。

时间复杂度为 O ( 1 ) O(1) O(1)

class LinkedList {
  // 省略其他实现

  isEmpty() {
    return this.head === null;
  }
}

插入实现

总共有三种:

  • 在头部插入,insertAtHead()
  • 在尾部插入,insertAtTail()
  • 在指定索引插入,insertAtTail()

头插

直接新建一个新的 Node,将链表原本的 Head 指向新的 Node,将链表的 Head 指向新的 Node

时间复杂度为 O(1)

图解如下:

  1. 原本的链表

    2 为原本的 Head

    2 3 Head null
  2. 新建一个 Node 作为需要插入到链表头部的 Node。

    1 2 3 null Head null
  3. 将原本的 Node 2 链接到新建的 Node 1

    1 2 3 null Head
  4. 更新链表的 Head

    1 2 3 Head null
class LinkedList {
  // 省略其他实现

  insertAtHead(data) {
    const newNode = new Node(data);
    newNode.next = this.head;
    this.head = newNode;
  }
}

尾插

根据接受的参数创一个新的 Node,随后找到链表最后一个结点,更新最后一个结点的 next

时间复杂度为 O ( n ) O(n) O(n),找到最后一个结点需要遍历整个链表。

class LinkedList {
  // 省略其他实现

  insertAtTail(data) {
    const newNode = new Node(data);

    if (this.isEmpty()) {
      return this.insertAtHead(data);
    }

    let curr = this.head;

    while (curr.nextElement !== null) {
      curr = curr.nextElement;
      console.log(curr);
    }

    curr.nextElement = newNode;
  }
}

中间插入

其实会了头插和尾差,在中间插入的实现就很简单了。

主要还是当索引在中间阶段的时候,需要断开原本结点之间的回路,并且创立新的回路:

1 2 3 4 head null

2 --> 4 中的回路会断开,转而会变成 2 --> 3 --> 4

断开 连接 连接 1 2 3 4 head null

最差情况下会使用尾差,所以这也是一个 O ( n ) O(n) O(n) 的操作。

class LinkedList {
  // 省略其他实现

  insertAt(index, data) {
    const newNode = new Node(data);

    // 当往头部插入,或者链表为空时,可以直接调用已经实现的函数
    if (index === 0 || this.isEmpty()) return this.insertAtHead(data);

    let curr = this.head,
      count = 1;

    while (curr.nextElement !== null && count < index) {
      count++;
      curr = curr.nextElement;
    }

    // 这里当插入的数据大于链表的长度时,直接选择在尾部插入。不过也可以通过抛出异常来提示错误
    if (count < index) return this.insertAtTail(data);

    const next = curr.nextElement;
    curr.nextElement = newNode;
    newNode.nextElement = next;
  }
}

搜索

搜索同样也是一个 O ( n ) O(n) O(n) 的操作,它需要完成对链表的遍历去寻找提供的值。

另外需要注意的是,搜索可以通过 遍历 和 递归的方式进行实现。便利查找的空间复杂度为 O ( 1 ) O(1) O(1),而递归的空间复杂度为 O(n)

class LinkedList {
  // 省略其他实现

  search(value) {
    let curr = this.head;

    while (curr !== null) {
      if (curr.value === value) return true;
      curr = curr.nextElement;
    }

    // 调用递归函数
    // this.recursiveSearch(this.head, value)

    return false;
  }

  recursiveSearch(node, value) {
    if (node === null) return false;
    if (node.value === value) return true;

    return this.recursiveSearch(node.nextElement, value);
  }
}

删除

和插入一样,删除也有几种不同的方式:

  • 头删
  • 尾删
  • 中间删除
  • 按值删除

头删

时间复杂度为 O ( 1 ) O(1) O(1)。

class LinkedList {
  // 省略其他实现

  deleteAtHead() {
    if (this.isEmpty()) return false;

    this.head = this.head.nextElement;
    return true;
  }
}

尾删

时间复杂度为 O ( n ) O(n) O(n)。

class LinkedList {
  // 省略其他实现

  deleteAtTail() {
    if (this.isEmpty()) return false;

    let curr = this.head;
    while (curr.nextElement.nextElement !== null) {
      curr = curr.nextElement;
    }

    curr.nextElement = null;

    return true;
  }
}

中间删除

时间复杂度为 O ( n ) O(n) O(n)。

class LinkedList {
  // 省略其他实现

  deleteAt(index) {
    if (this.isEmpty()) return false;

    let count = 1,
      curr = this.head;

    if (index === 0) return this.deleteAtHead();

    while (count < index && curr !== null) {
      curr = curr.nextElement;
      count++;
    }

    if (curr === null) return false;

    // 注意空值的问题
    curr.nextElement = curr.nextElement ? curr.nextElement.nextElement : null;
    return true;
  }
}

按值删除

其实还是搜索的逻辑,确认搜索的值是否是需要删除的值,随后再进行操作,时间复杂度为 O ( n ) O(n) O(n)。

class LinkedList {
  // 省略其他实现

  deleteBy(value) {
    if (this.isEmpty()) return false;

    let curr = this.head;
    if (curr.value === value) {
      this.head = curr.nextElement;
      return true;
    }

    while (curr.nextElement !== null) {
      if (curr.nextElement.value === value) {
        curr.nextElement = curr.nextElement.nextElement;
        return true;
      }
      curr = curr.nextElement;
    }

    return false;
  }
}

获取长度

有两种方式可以实现,第一种就是通过遍历的方式去获取链表的长度;第二种则是在保存一个链表长度的变量,每次操作的时候都需要更新这个变量。

这里就不修改之前已经写好的代码了,直接通过遍历的方式获取链表的长度,时间复杂度为 O ( n ) O(n) O(n)。

class LinkedList {
  // 省略其他实现
  length() {
    let length = 0;

    let curr = this.head;

    while (curr !== null) {
      length++;
      curr = curr.nextElement;
    }

    return length;
  }
}

翻转链表

本质上来说还是遍历链表,这个操作需要保留 3 个变量:prev, curr, next,交替更新。

原本的顺序为 prev -> curr -> next,在遍历到 curr 时需要进行通过 next 去保留地址,随后对 currprev 进行翻转操作,将其更新为 curr -> prev。一直到结束遍历,最后更新 this.head 即可。

时间复杂度为 O(n)

  • 原链表

    1 2 3 4 null null prev next curr head null
  • 进入遍历,将 next 指向下一个结点,用以保留当前位置。同时反转 prevcurr

    1 2 3 4 null prev next curr head null

    此时链表本身的结构为:

    1 2 3 4 null head null
  • 更新 prevcurr 的位置

    1 2 3 4 null prev next curr head null
  • 进入下一个遍历,更新 next 的指向

    1 2 3 4 null prev next curr head null
  • 继续反转 currprev 之间的关系

    1 2 3 4 null prev next curr head null

    此时链表的结构为:

    1 2 3 4 null head null

    可以看到,next 之前的结点已经实现了翻转。

  • 继续重复这个过程一直到 curr 指向空

    1 2 3 4 null prev head null curr next
  • 最后将 this.head 指向 prev,实现链表的翻转

    1 2 3 4 null prev head null curr next
class LinkedList {
  // 省略其他实现
  reverse() {
    let prev = null,
      curr = this.getHead(),
      next = null;

    while (curr !== null) {
      next = curr.nextElement;
      curr.nextElement = prev;
      prev = curr;
      curr = next;
    }

    this.head = prev;
  }
}

其余有趣的实现

也是一些常见的面试题,包括:

  • 检查链表中是否存在环路 (双指针)
  • 寻找链表中间的结点 (双指针)
  • 移除链表中重复的数据 (哈希表)
  • 寻找两个链表的并集和交集 (哈希表)
  • 返回倒数第 N 个结点

标签:II,head,curr,按值,nextElement,链表,return,null
来源: https://blog.csdn.net/weixin_42938619/article/details/120374745

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有