开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
前言
本系列文章主要会总结一些常见的算法题目以及算法的易错点,难点,以及一些万用的公式,并且结合实际的 Leetcode 题目来进行加深理解以及实际应用,算法这种东西,属于是一到用时方恨少的类型,在平时总结一些常见的简单算法,经常磨练自己的算法思维,对于日常的开发还是能有不少的帮助的。
- 今天来介绍一下双链表
什么是双链表
双向链表又称为双链表,是链表的一种,它和单链表的区别就在于,它的每一个数据结点中都有两个指针,分别指向前一个结点和后一个结点,所以我们可以很方便的从双链表中的某个数据,访问它的前驱结点和后继结点。
在实际代码中,如何去理解双链表
用一个类来描述一下双链表的某一个结点就是 :
function Node(element) {
this.element = element;
this.next = null;
this.previous = null;
}
其中 next 指向的就是下一个结点,previous 指向的就是下一个结点。
所以实际打印出来的双链表,是表示不完的,试想一下,第一个结点的 next 为下一个结点也就是第二个结点对象,第二个结点对象的 previous 属性又指向前一个也就是第一个结点对象,会一直嵌套循环,永远都表示不完,这也是双链表的特点,根据链表中的某一个位置都可以去寻找前驱结点或者后继结点。
那么在理解了双链表的结构以后,我们来看一下 Leetcode 的题目 707. 设计链表
Leetcode 707. 设计链表
设计一个链表,用双链表或者单链表都可以,既然介绍了双链表的结构,那么就用双链表来解决一下这道题。
链表结点包含三个属性 val,next 和 prev 三个属性,分别就是当前的结点数据,下一个结点和上一个结点。
题目需要实现5个功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
那么就根据上面所说的双链表的结构,来一步步的完成这五个功能。
并且双向链表的话,在添加新节点或者是移除的时候,不同于单链表,需要对前后两个结点的 前驱结点 和 后继结点 都要进行改变,如下图:
移除也是同理的,并且双链表在查找的时候相比于单链表存在性能上的优势,可以通过查找的下标的更接近头部或者尾部来决定从头开始查找还是从尾开始查找。
那么根据双链表的特性,优先构建双链表,可以在新建结点的时候,可以初始化结点的前一个结点或者后一个结点,并且在双链表中可以维护当前的头结点和尾结点,这样能够方便上面的在头部新加和在尾部新加的两个操作。
class MyNode {
val: number
pre: MyNode | null
next: MyNode | null
constructor(val: number, pre: MyNode = null, next: MyNode = null) {
this.val = val
this.pre = pre
this.next = next
}
addRight(node: MyNode) {
node.next = this.next
if (node.next != null) {
node.next.pre = node
}
this.next = node
node.pre = this
}
}
class MyLinkedList {
size: number
head: MyNode
tail: MyNode
constructor() {
this.size = 0
this.head = new MyNode(-1)
this.tail = new MyNode(-1, this.head)
this.head.next = this.tail
}
getNode(index: number): MyNode {
if (index < 0 || index >= this.size) {
return null
}
let node: MyNode
if (index * 2 > this.size) {
node = this.tail
for (let i = 0; i < this.size - index; i++) {
node = node.pre
}
} else {
node = this.head
for (let i = 0; i <= index; i++) {
node = node.next
}
}
return node
}
get(index: number): number {
const node: MyNode = this.getNode(index)
return node == null ? -1 : node.val
}
addAtHead(val: number): void {
this.head.addRight(new MyNode(val))
this.size++
}
addAtTail(val: number): void {
this.tail.pre.addRight(new MyNode(val))
this.size++
}
addAtIndex(index: number, val: number): void {
if (index <= this.size) {
if (index <= 0) {
this.addAtHead(val)
} else if (index == this.size) {
this.addAtTail(val)
} else {
this.getNode(index).pre.addRight(new MyNode(val))
this.size++
}
}
}
deleteAtIndex(index: number): void {
const node: MyNode = this.getNode(index)
if (node != null) {
node.pre.next = node.next
if (node.pre.next != null) {
node.pre.next.pre = node.pre
}
this.size--
}
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/