LeetCode - 链表
PS:按照代码随想录的题单顺序刷题,记录一下。
1. 基础概念
见链表。
2. 移除链表元素
描述
LeetCode203 - 给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点。简单
示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
思路
这题思路很简单,遍历把目标元素删除即可,但代码并不好写,需要注意给定的结构没有头结点。
203 - 移除链表元素
var removeElements = function(head, val) {
// 设置一个头节点便于统一操作
const newHead = new ListNode();
newHead.next = head;
let current = newHead;
while(current.next != null) {
if (current.next.val == val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return newHead.next;
};
3. 设计链表
描述
LeetCode707 - 实现 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
的节点。中等
示例1:
输入:["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出:[null, null, null, null, 2, null, 3]
思路
属于链表基础,需要熟悉。
707 - 设计链表
var MyLinkedList = function() {
this.head = undefined;
this.count = 0;
};
const Node = function(element) {
this.val = element;
this.next = undefined;
}
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
for(let i = 0; i < index; i++) {
current = current.next;
}
return current.val;
}
return -1;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
const node = new Node(val);
let current = this.head;
node.next = current;
this.head = node;
this.count++;
return true;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
const node = new Node(val);
if (this.head == null) {
this.head = node;
} else {
let current = this.head;
while(current.next != null) {
current = current.next;
}
current.next = node;
}
this.count++;
return true;
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
const node = new Node(val);
if(index >= 0 && index <= this.count) {
if (index == 0) {
node.next = this.head;
this.head = node;
} else {
let current = this.head;
let previous;
for(let i = 0; i < index; i++) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node
}
this.count++;
return true;
}
return false;
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
if (index >= 0 && index < this.count) {
if (index == 0) {
this.head = this.head.next;
} else {
let current = this.head;
let previous;
for(let i = 0; i < index; i++) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.count--;
return true;
}
return false
};
4. 反转链表
描述
LeetCode206 - 给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。简单
示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
思路
构造个新链表,依次遍历并将元素插入新链表的第一个位置。
双指针:使用两个指针分别保存上一元素和下一元素。
206 - 反转链表
var reverseList = function(head) {
if(!head) return null;
let list = new ListNode(head.val, null);
let current = head.next;
while(current) {
const node = new ListNode(current.val, null);
node.next = list;
list = node;
current = current.next;
}
return list;
};
// 双指针
var reverseList = function(head) {
if(!head) return head;
let prev = null, after, current = head;
while(current) {
after = current.next;
current.next = prev;
prev = current;
current = after;
}
return prev;
};
5. 两两交换链表中的节点
描述
LeetCode24 - 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即只能进行节点交换)。中等
示例1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例2:
输入:head = []
输出:[]
思路
依次遍历,两个两个元素的比较,如果后面不存在两个元素则结束循环,存在则交换位置,可以创建个虚拟头节点来统一操作。
24 - 两两交换链表中的节点
// 虚拟节点
var swapPairs = function(head) {
if(!head || !head.next) return head;
// 使用头节点统一操作节点后两个元素
const res = new ListNode();
res.next = head;
let current = res, first, second;
while(current.next) {
first = current.next;
second = current.next.next;
if (second) {
first.next = second.next;
second.next = first;
current.next = second;
current = first;
} else {
break;
}
}
return res.next;
};
6. 删除链表的倒数第 N 个结点
描述
LeetCode19 - 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。中等
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例2:
输入:head = [1], n = 1
输出:[]
思路
定义双指针,右指针先前进 n
步,左指针再跟着遍历,当右指针遍历到末尾时,左指针指向的就是要删除的元素的前一节点。大多数链表题可以设置虚拟头节点,便于操作。
19 - 删除链表的倒数第 N 个结点
var removeNthFromEnd = function(head, n) {
const dummy = new ListNode();
dummy.next = head;
// 双指针
let left = dummy, right = dummy;
for(i = 0; i < n; i++) {
right = right.next;
}
while(right.next) {
right = right.next;
left = left.next
}
left.next = left.next.next;
return dummy.next;
};
7. 相交链表
描述
LeetCode160 - 给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。简单
示例1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
思路
哈希表:使用哈希表存储一个链表的元素,遍历另一链表判断是否有相同节点。
双指针:分别遍历两个链表并比较,到达最后一个元素将指针指向另一个链表。
160 - 相交链表
// 双指针
var getIntersectionNode = function (headA, headB) {
let currentA = headA, currentB = headB;
let transforA = true, transforB = true;
while (currentA && currentB) {
if (currentA === currentB) {
return currentA;
}
currentA = currentA.next;
currentB = currentB.next;
if (transforA && !currentA) {
currentA = headB;
transforA = false;
}
if(transforB && !currentB) {
currentB = headA;
transforB = false;
}
}
return null;
};
// 哈希表
var getIntersectionNode = function(headA, headB) {
const map = new Map();
let current = headA;
while(current) {
map.set(current, 1);
current = current.next;
}
current = headB;
while(current) {
if(map.has(current)) {
return current;
}
current = current.next;
}
return null;
};
8. 环形链表II
描述
LeetCode142 - 给定一个链表的头节点 head
,返回链表开始入环的第一个节点。如果链表无环,则返回 null
。中等
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。pos 表示尾部指向的节点索引,为 -1 表示无环。
思路
首先想到的就是用哈希表或集合将遍历过的节点保存,时间和空间复杂度都是 O(n)
。还有个快慢指针版本,其中快指针每次走两步,慢指针每次一步,二者必定会在环内相遇(这里可能不好理解),经过数学推理可以得出,慢指针从环内相遇点继续向下走,同时用第三个指针从头指针开始出发,二者相遇的节点就是开始入环的节点。
142 - 环形链表II
// 哈希表
var detectCycle = function(head) {
const map = new Map();
let current = head;
while(current) {
if (map.get(current)) {
return current;
}
map.set(current, 1);
current = current.next;
}
return null;
};
// 快慢指针
var detectCycle = function(head) {
if (!head) return null;
let slow = head, fast = head;
while(fast) {
slow = slow.next;
if(!fast.next) {
return null;
} else {
fast = fast.next.next;
}
if(fast === slow) {
let cur = head;
while(cur !== slow) {
cur = cur.next;
slow = slow.next;
}
return cur;
}
}
return null;
};