Day3-链表,leetcode203,707,206
链表
理论
链表类型
-
链表,通过指针串联在一起的线性结构。分为单链表、双链表、循环链表。
-
单链表:每一个节点包含2部分,左、右,左部分是内容区,右部分是指针,指针用于指向下一个节点,最后一个节点的指针指向null。
- 遍历终止条件:遇到null停止
while(current !== null)
。
- 遍历终止条件:遇到null停止
-
双链表:每一个节点包含3个部分,左、中、右,左部分指向上一个节点,中部分是内容区,右部分指向下一个节点。首节点的左指针指向null,尾节点的右指针指向null。
- 正向遍历,终止条件:
current.next === nul
- 反向遍历,终止条件:
current.prev === null
- 正向遍历,终止条件:
-
循环链表:链表首尾相连,是一个环形结构。存在循环单链表、循环双链表。尾节点指针指向头节点。
- 循环单链表,遍历终止条件:回到起点时停止
while(current.next !== head)
。 - 循环双链表,遍历终止条件:(1)正向遍历,
current.next === head
;(2)反向遍历,current.prev === head
- 循环单链表,遍历终止条件:回到起点时停止
链表存储方式
- 数组是在内存中连续分布的,而链表在内存中可不是连续分布的,链表通过指针指向内存中的各个节点
链表定义方式
// 使用类ES6
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
const node11 = new ListNode(1)
const node22 = new ListNode(2, node11)
性能分析
- 数组,插入/删除时间复杂度O(n),查询时间复杂度O(1),适用于数据量固定,频繁查询,较少增删;
- 链表,插入/删除时间复杂度O(1),查询时间复杂度O(n),适用于数据量不固定,频繁增删,较少查询;
链表的操作
- 删除节点
- 修改被删除节点的前一个节点的next为被删除节点的next
- 添加节点
- 添加的新节点会添加到链表的尾部
- 链表为空,则新节点设置为头节点;链表不空,遍历到最后一个节点,即current.next为null到节点,然后把新节点挂到最后一个节点的next上
// ListNode类:同样定义了链表节点的结构,包含 val 和 next 属性。
class ListNode {
constructor(val = 0, next = null) {
// 定义链表节点类
this.val = val;
this.next = next;
}
}
// LinkedList类:addNode 方法向链表添加新节点,deleteNode 方法用于删除链表中指定值的节点。
class LinkedList {
constructor() {
// 初始化链表,设置头节点
this.head = null
}
addNode(val) {
const newNode = new ListNode(val)
if (!this.head) {
this.head = newNode;
return;
}
let current = this.head
while (current.next) {
current = current.next
}
current.next = newNode
}
deleteNode(val) {
if (!this.head) {
return
}
if (this.head.val === val) {
this.head = this.head.next
return
}
let current = this.head
while (current.next) {
if (current.next.val === val) {
current.next = current.next.next
return
}
current = current.next
}
}
}
// 测试代码
const linkedList = new LinkedList();
linkedList.addNode(1);
linkedList.addNode(2);
linkedList.addNode(3);
console.log('linkedList: ', linkedList);
console.log("删除节点前:");
let current = linkedList.head;
while (current) {
console.log(current.val);
current = current.next;
}
linkedList.deleteNode(2);
console.log("删除节点后:");
current = linkedList.head;
while (current) {
console.log(current.val);
current = current.next;
}
- 移除链表元素
-
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点
-
思路:
-
删除时要分是否是头节点,删除的是头节点,则将头节点设置为头节点的next,遍历,
while(head!=null && head.val === target) head=head.next
;删除的不是头节点的话,直接将当前节点的next设置为next.next,current.next = current.next.next
-
为了统计思路,可以在头节点前面增加一个虚拟头节点
-
设置当前值从头节点开始,因为单链表,不能往前找,只能往后找,所以要设置初始节点为当前节点;
-
返回的是虚拟头节点的next,而不是head,因为虚拟头节点一直存在,而head可能被删除了;
// 区分是否是头节点
var removeElements = function(head, val) {
// 1. 先处理头节点连续等于 val 的情况
while (head != null && head.val === val) {
head = head.next
}
// 2. 再处理非头节点
let current = head;
while (current !== null && current.next !== null) {
if (current.next.val === val) {
current.next = current.next.next
} else {
current = current.next
}
}
return head
};
// 统一思路,头节点前面增加一个节点
var removeElements = function(head, val) {
// new ListNode(0, head) 的意思是:创建一个值为 0、next 指向 head 的链表节点。
// 0 是节点的值(val),head 是原链表的头节点,作为新节点的 next 指针。
// 这样做的目的是引入一个虚拟头节点,方便统一处理链表头节点被删除的情况,避免特殊判断。
const ret = new ListNode(0, head)
// cur 指针从虚拟头节点开始遍历
let cur = ret;
// 遍历链表
while(cur.next) {
// 如果下一个节点的值等于 val,则跳过该节点
if(cur.next.val === val) {
cur.next = cur.next.next
// 跳过后不移动 cur,继续检查新的 cur.next
continue
}
// 否则,cur 指针后移
cur = cur.next
}
// 返回新链表的头节点(去掉虚拟头节点)
return ret.next
}
- 设计链表
-
你可以选择使用单链表或者双链表,设计并实现自己的链表。
-
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
-
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
-
实现 MyLinkedList 类:
- MyLinkedList() 初始化 MyLinkedList 对象。
- int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
- void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
- void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
- void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
- void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。
-
思路:
-
初始化链表,定义虚拟头节点
-
get(index),获取到第index节点,如果index非数值直接返回-1,index是从0开始的,第0个节点就是头节点
-
addAtHead,在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头节点
-
addAtTail,在链表最后面添加一个节点
-
addAtIndex,在第index节点之前插入一个新节点,如index为0,则新插入的节点为链表的新头节点,若index等于链表的长度,则新插入的节点为链表的尾节点,若index大于链表的长度,则返回空,若index小于0,则在头部插入节点
-
deleteAtIndex,删除第index个节点,若index大于等于链表的长度,直接return,注意index是从0开始的
// 单链表
class LinkNode {
constructor(val, next) {
this.val = val;
this.next = next;
}
}
// 单链表,储存头尾节点和节点数量
var MyLinkedList = function() {
this._size = 0;
this._tail = null;
this._head = null;
}
MyLinkedList.prototype.getNode = function(index) {
if (index < 0 || index >= this._size) return null
// 创建虚拟头节点
let cur = new LinkNode(0, this._head)
while(index-- >= 0) {
cur = cur.next
}
return cur
}
MyLinkList.prototype.get = function(index) {
if (index < 0 || index >= this._size) return -1
// 获取当前节点
return this.getNode(index).val
}
MyLinkedList.prototype.addAtHead = function(val) {
const node = new LinkNode(val, this._head)
this._head = node
this._size++
if(!this._tail) {
this._tail = node
}
}
MyLinkedList.prototype.addAtTail = function(val) {
const node = new LinkNode(val, null)
this._size++
if(this._tail) {
this._tail.next = node;
this._tail = node
return
}
this._tail = node
this._head = node
}
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index > this._size) return
if (index <= 0) {
this.addAtHead(val)
return
}
if (index === this._size) {
this.addAtTail(val)
return
}
// 获取目标节点的上一个节点
const node = this.getNode(index - 1)
node.next = new LinkNode(val, node.next)
this._size++
}
MyLinkedList.prototype.deleteAtIndex = function(index) {
if (index < 0 || index >= this._size) return
if (index === 0) {
this._head = this._head.next
// 如果删除的这个节点同时是尾节点,要处理尾节点
if (index === this._size - 1) {
this._tail = this._head
}
this._size--
return
}
// 获取目标节点的上一个的节点
const node = this.getNode(index - 1)
node.next = node.next.next
// 处理尾节点
if (index === this._size - 1) {
this._tail = node
}
this._size--
}
// 双链表,包含前指针prev,后指针next
class Node {
constructor(val, prev, next) {
this.val = val;
this.prev = prev;
this.next = next;
}
}
// 双链表,维护head、tail两个哨兵,size
var MyLinkedList = function() {
this.tail = new Node(-1)
this.head = new Node(-1)
this.tail.prev = this.head
this.head.next = this.tail
this.size = 0
}
// 获取在index处节点的值
MyLinkedList.prototype.get = function (index) {
if (index > this.size) {
return -1
}
let cur = this.head
for (let i = 0; i <= index; i++) {
cur = cur.next
}
return cur.val
}
// 在链表头部添加一个新节点
MyLinkedList.prototype.addAtHead = function(val) {
this.size++
const originNode = this.head.next
// 创建新节点,并建立连接
const newNode = new Node(val, this.head, originNode)
// 取消原前后节点的连接
this.head.next = newNode
originNode.prev = newNode
}
// 在链表尾部添加一个新节点
MyLinkedList.prototype.addAtTail = function (val) {
/**
originNode <-> [newNode] <-> tail
*/
this.size++
const originNode = this.tail.prev
// 创建新节点,并建立连接
const newNode = new Node(val, originNode, this.tail)
// 取消原前后结点的连接
this.tail.prev = newNode
originNode.next = newNode
};
// 在指定索引位置前添加一个新节点
MyLinkedList.prototype.addAtIndex = function (index, val) {
// 当索引超出范围时,直接返回
if (index > this.size) {
return
}
this.size++
let cur = this.head
for (let i = 0; i < index; i++) {
cur = cur.next
}
const new_next = cur.next
// 创建新节点,并建立连接
const node = new Node(val, cur, new_next)
// 取消原前后结点的连接
cur.next = node
new_next.prev = node
};
// 删除指定索引位置的节点
MyLinkedList.prototype.deleteAtIndex = function (index) {
// 当索引超出范围时,直接返回
if (index >= this.size) {
return
}
this.size--
let cur = this.head
for (let i = 0; i < index; i++) {
cur = cur.next
}
const new_next = cur.next.next
// 取消原前后结点的连接
new_next.prev = cur
cur.next = new_next
};
- 反转链表
- 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
// 双指针
var reverseList = function(head) {
if (!head || !head.next) return head
let temp = null, pre = null, cur = head
while(cur) {
// 记录curr下一节点的位置
temp = cur.next
// 反转节点间指针的方向
cur.next = pre
// 将prev指针向后移动一位
pre = cur
// 将curr指针向后移动一位
cur = temp
}
return pre
}
// 递归:
var reverse = function(pre, head) {
if(!head) return pre;
const temp = head.next;
head.next = pre;
pre = head
return reverse(pre, temp);
}
var reverseList = function(head) {
return reverse(null, head);
};
// 递归2
var reverse = function(head) {
if(!head || !head.next) return head;
// 从后往前翻
const pre = reverse(head.next);
head.next = pre.next;
pre.next = head;
return head;
}
var reverseList = function(head) {
let cur = head;
while(cur && cur.next) {
cur = cur.next;
}
reverse(head);
return cur;
};