链表理论基础
什么是链表?
链表是一种通过指针串联在一起的线性结构。每个节点由两部分组成:数据域、指针域(存放指向下一个节点的指针)。入口节点称为链表的头节点(head),最后一个节点的指针域指向null(空指针)。
如图:
链表的类型
单链表、双链表、循环链表
链表的存储方式
链表在内存中不是连续分布的。
链表通过指针域的指针 链接内存中的各个节点。
链表的定义
面试时,手写链表:
class ListNode {
val;
next = null;
constructor(value) {
this.val = value;
this.next = null;
}
}
链表的操作
删除节点
将目标节点的上一节点指针指向其下一节点。
如图:
添加节点
如图:
203.移除链表元素
题目链接:203.移除链表元素
解题思路:
1、直接使用原来的链表来进行删除操作。
2、设置一个虚拟头结点再进行删除操作。
若目标节点为头结点,将头结点向后移动一位即可。但是需要单独写一段逻辑来处理移除头结点的情况。
可以给链表添加一个虚拟头结点为新的头结点,使用与其他结点相同的方式将结点删除。cur.next = cur.next.next
代码⌨️
var removeElements = function(head, val) {
const ret = new ListNode(0, head); //创建虚拟头节点,指向head节点
let cur = ret; //设置辅助指针指向虚拟节点
while(cur.next) {
if(cur.next.val === val) {
cur.next = cur.next.next;
continue;
}
cur = cur.next;
}
return ret.next; //直接返回ret.next就能打印整条链表
};
这里要注意:链表一定要分清节点和指针的概念 new ListNode()是真实存在的一个节点, head = new ListNode() 相当于 head指针指向了一个真实的节点, node = head, 相当于node和head同时指向了这个真实的节点。
总结
链表中的头结点比较特殊,需要多加注意!
707.设计链表
题目链接:707.设计链表
解题思路:
设置虚拟头结点进行操作。
代码⌨️
class LinkNode {
constructor(val, next) {
this.val = val;
this.next = next;
}
}
/**
* Initialize your data structure here.
* 单链表 储存头尾节点 和 节点数量
*/
var MyLinkedList = function() {
this._size = 0;
this._tail = null;
this._head = null;
};
/**
* Get the value of the index-th node in the linked list. If the index is invalid, return -1.
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.getNode = function(index) {
if(index < 0 || index >= this._size) return null;
// 创建虚拟头节点
let cur = new LinkNode(0, this._head);
// 0 -> head
while(index-- >= 0) {
cur = cur.next;
}
return cur;
};
MyLinkedList.prototype.get = function(index) {
if(index < 0 || index >= this._size) return -1;
// 获取当前节点
return this.getNode(index).val;
};
/**
* 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.
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
const node = new LinkNode(val, this._head);
this._head = node;
this._size++;
if(!this._tail) {
this._tail = node;
}
};
/**
* Append a node of value val to the last element of the linked list.
* @param {number} val
* @return {void}
*/
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;
};
/**
* 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.
* @param {number} index
* @param {number} val
* @return {void}
*/
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++;
};
/**
* 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._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--;
};
206.反转链表
题目链接:206.反转链表
解题思路:
只需要改变链表的next指针的指向,直接将链表反转,而不用重新定义一个新的链表。
代码⌨️
双指针法:
var reverseList = function(head) {
//判断链表是否为空
if(!head || !head.next) return head;
// 初始化前驱结点为 null | 初始化目标结点为头结点
let temp = null, pre = null, cur = head;
while(cur) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
// temp = cur = null;
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;
};
总结
代码中怎么链表是否为空,
if(!head || !head.next) return head;。如果链表为空,那么head就是null,那么!head就是True了,即可对空指针进行后续操作。
今日小结
训练营的第三天!
处理链表的本质,是处理链表结点之间的指针关系。
第四天也要加油鸭!🥰