链表理论基础
- 链表中的元素在内存在并不是连续存放的,而数组是需要开辟固定大小的内存空间;
- 所以链表对内存空间的利用率更高;
- 数组可通过下标访问元素,链表只能从表头开始迭代;
- 链表在添加或移除元素的时候不需要移动其他元素,而数组牵一发而动全身。
LeetCode 203.移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
- 思路:
- 法1:直接遍历,然后找到
val相等的结点。 - 法2:创建哑结点,也就是带头结点的链表,这样就不用单独考虑原来链表的
head.val === val的情况了。
- 法1:直接遍历,然后找到
- 注意点:链表中删除某个元素的操作,要先迭代的条件是 当前结点p不是
null,所以while的条件是p.next存在。
不带头结点(不带哑结点)
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
if(!head){ // 空链表
return head;
}
let p = head;
while(p.next){
if(p.next.val === val){
p.next = p.next.next; // 移除
}else{
p = p.next;
}
}
if(head.val === val){ // 删除头结点
head = head.next;
}
return head;
};
带头结点链表(带哑结点)
var removeElements = function(head, val) {
let dummyNode = new ListNode(); // 创建哑结点
dummyNode.next = head;
let p = dummyNode;
while(p.next){
if(p.next.val === val){
p.next = p.next.next;
}else{
p = p.next;
}
}
return dummyNode.next; // 第一个结点是空的,所以返回的是下一个
};
LeetCode 707.设计链表
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
- get(index):获取链表中第
index个节点的值。如果索引无效,则返回-1。 - addAtHead(val):在链表的第一个元素之前添加一个值为
val的节点。插入后,新节点将成为链表的第一个节点。 - addAtTail(val):将值为
val的节点追加到链表的最后一个元素。 - addAtIndex(index,val):在链表中的第
index个节点之前添加值为val的节点。如果index等于链表的长度,则该节点将附加到链表的末尾。如果index大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。 - deleteAtIndex(index):如果索引
index有效,则删除链表中的第index个节点。
-- 思路:任何一种数据结构的基本操作都是增删改查,这里主要是增删查,本题难点在增和删。不带表头结点的做法会比带表头结点的多出一个操作:单独考虑对头结点的操作,比如删除头结点。
-- 注意点:增删操作,每次需要更新长度size属性。
其实这题带不带哑结点都无所谓~~
单链表(不带头结点)
// 结点
var ListNode = function(val, next){
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
// 链表
var MyLinkedList = function() {
this.size = 0;
this.head = new ListNode(0);
};
// 不带表头结点版本
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function(index) {
if(index < 0 || index > this.size - 1){
// 下标越界
return -1;
}
let cur = this.head;
for(let i = 0; i < index; i ++){
// 遍历,直到index下标
cur = cur.next
}
return cur.val;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
this.addAtIndex(0, val);
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
this.addAtIndex(this.size, val);
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index <= this.size){ // 检查下标是否合法
let cur = this.head;
let q = new ListNode(val);
if(index <= 0){ // 插入头结点之前
q.next = this.head;
this.head = q;
}else{ // 其他地方插入新结点
for(let i = 0; i < index - 1; i ++){
cur = cur.next;
}
// 插入结点
q.next = cur.next;
cur.next = q;
}
this.size += 1;
}
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
if(index >= 0 && index < this.size){ // 检查下标合法性
if(index === 0){ // 删除头结点
this.head = this.head.next;
}else{ // 其他结点的删除
let count = 0;
let prev ; // 记录一个前驱结点
let cur = this.head;
while(count < index){
prev = cur;
cur = cur.next;
count += 1;
}
prev.next = cur.next;
}
this.size -= 1;
}
};
LeetCode 206.反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
- 思路:
- 新设置一个空链表,任何倒置插入元素?
- 这样太耗空间,建议原地反转。需要记录前置结点。
- 注意点:最后返回的是
prev,不是head,经过反转,prev已经是原链表的最后一个非null结点,head还在原来链表的第一个元素,也不是返回cur,此时的cur已经指向最后的null了。 - 代码流程图示(图中数字和代码注释中数字对应):
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
if(!head || !head.next){ // 可以先排除掉空链表和只有一个元素的链表
return head;
}
let cur = head; // cur用来遍历链表 ①
let prev = null; // ②
while(cur){
let temp = cur.next; //③ 先保存这个结点,否则后续改变指向后找不到 cur.next,将断链
cur.next = prev; // cur.next指向前一个元素④
prev = cur; // prev继续往后⑤
cur = temp; // cur继续往后⑥
}
return prev; // 返回的是prev,这时候prev已经到最后的头了,head还在一开始的位置,也就是新的尾部
};