难度标识:
⭐:简单,⭐⭐:中等,⭐⭐⭐:困难。
tips:这里的难度不是根据LeetCode难度定义的,而是根据我解题之后体验到题目的复杂度定义的。
1.相交链表 ⭐
思路
解这题的思路可以使用双指针。首先拿一个指针遍历链表A,当遍历到链表A末尾之后,让指针再遍历B,在遍历链表A的同时,拿另一个指针去遍历链表B,当遍历到链表B的末尾时,再去遍历链表A,当这两个指针相遇时,那这个点就是两个链表相交的节点。具体原理见下图:
第1个指针遍历的总长度是:a+b+c
第2指针遍历的总长度是:c+b+a
a+b+c = c+b+a,所有两个指针相遇的点就是两个链表相交。
代码
var getIntersectionNode = function (headA, headB) {
let pA = headA, pB = headB
while (pA !== pB) {
pA = pA ? pA.next : headB
pB = pB ? pB.next : headA
}
return pA
};
2.反转链表 ⭐
思路
解这题可以使用3个指针,通过迭代调整链表中的指针来实现反转。具体思路步骤:
-
初始化指针:
prev指针初始化为null。curr指针初始化为head。next指针初始化为null或者不需要立即初始化。
-
遍历链表:
- 在每次迭代中,首先保存
curr的下一个节点到next(这样我们就可以安全地更改curr的next指针)。 - 更改
curr的next指针指向prev,完成一个节点的反转。 - 移动
prev和curr指针到下一个位置。具体来说,prev移动到curr的位置,curr移动到next的位置。
- 在每次迭代中,首先保存
-
迭代完成后:
- 当
curr为null时,遍历结束。此时,prev将指向新的头节点。
- 当
代码
var reverseList = function (head) {
let prev = null, curr = head, next;
while (curr) {
next = curr.next
curr.next = prev
prev = curr
curr = next
}
return prev
};
3.回文链表 ⭐
思路
这题可以把链表的值放到数组里,然后通过数组去判断链表是否是回文链表。
代码
var isPalindrome = function (head) {
const arr = []
while (head) {
arr.push(head.val)
head = head.next
}
let left = 0, right = arr.length - 1;
while (left < right) {
if (arr[left] !== arr[right]) {
return false
} else {
left++
right--
}
}
return true
};
4. 环形链表 ⭐
思路
解这题也可以通过双指针来解决,而且是快慢双指针。拿一个慢指针,一个快指针,快指针每次走2步,而慢指针每次走1步,如果快指针和慢指针相遇了,那一定是有环的。然后这一题和下一题想要了解的更清楚可以看下这个视频。
代码
var hasCycle = function (head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next
fast = fast.next.next
if (slow === fast) {
return true
}
}
return false
};
5.环形链表 II ⭐
思路
这题是在上面那题的基础上的题目,然后这题就是找到上面那题开始入环的第一个节点。然后解这题的思路就是先确认有没有环,有环的话再找到入环的第一个节点。那怎么找到这个入环的起点呢?上面我们通过快慢指针找环,当找到环时,我们将慢指针指向链表开头,然后快指针和慢指针每次走一步,当它们相遇时,那么这个节点就是入环的第一个节点。具体的数学证明和思路讲解可以看这个视频。
代码
var detectCycle = function (head) {
let slow = head, fast = head
while (fast && fast.next) {
slow = slow.next
fast = fast.next.next
if (fast === slow) {
slow = head
while (slow !== fast) {
slow = slow.next
fast = fast.next
}
return slow
}
}
return null
};
6.合并两个有序链表 ⭐
思路
解这题也可以使用双指针的思路。对两个链表都设置一个指针,然后比较它们当前的值,将较小值的节点添加到新的链表中,并移动那个较小值的指针。
代码
var mergeTwoLists = function (list1, list2) {
let dummy = new ListNode(0)
let curr = dummy
while (list1 && list2) {
if (list1.val < list2.val) {
curr.next = list1
list1 = list1.next
} else {
curr.next = list2
list2 = list2.next
}
curr = curr.next
}
curr.next = (!list1 ? list2 : list1)
return dummy.next
};
7.两数相加 ⭐
思路
解这题就是一个模拟的思路,就是模拟加法计算,拿一个变量去记录进位。核心思路就是模拟数字的加法运算过程。考虑到链表表示的数字是逆序存储的,这为我们提供了方便,因为我们可以直接从链表的头部开始模拟加法的过程。
代码
var addTwoNumbers = function (l1, l2) {
let dummy = new ListNode(0)
let curr = dummy, count = 0
while (l1 || l2 || count) {
const sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + count
count = sum > 9 ? 1 : 0
curr.next = new ListNode(sum % 10)
curr = curr.next
l1 = l1 && l1.next
l2 = l2 && l2.next
}
return dummy.next
};
8.删除链表的倒数第 N 个结点 ⭐
思路
解这题也是使用双指针的思路。拿一个快指针先走n+1步,然后再拿一个慢指针,快指针慢指针一起走,当快指针走到末尾时,慢指针的位置就是倒数第n+1个位置,然后将这个位置的指针指向改到下下个结点即可,这样就删掉了倒数第N个结点。
代码
var removeNthFromEnd = function (head, n) {
let dummy = new ListNode(0, head)
let slow = dummy, fast = dummy
for (let i = 0; i < n + 1; i++) {
fast = fast.next
}
while (fast) {
slow = slow.next
fast = fast.next
}
slow.next = slow.next.next
return dummy.next
};
9.两两交换链表中的节点 ⭐⭐
思路
解下面这题的思路也是使用指针。这个问题的核心思路是使用指针来帮助我们完成节点之间的交换。你可以考虑使用三个指针:prev, curr, 和 next。这三个指针将帮助我们迭代链表,并在每一步中完成节点的交换。如果想要了解这题思路的具体细节,可以看下面这个视频,讲的很清晰。
代码
var swapPairs = function (head) {
let dummy = new ListNode(0, head)
let curr = dummy
while (curr.next && curr.next.next) {
let first = curr.next, third = curr.next.next.next
curr.next = curr.next.next
curr.next.next = first
first.next = third
curr = curr.next.next
}
return dummy.next
};
10.K 个一组翻转链表 ⭐⭐
思路
解决这题的思路同样是使用指针。首先上面的第2题是反转链表,既然我们会了反转链表,那就根据k分组即可,然后分组反转,前后连接起来即可。这题如果你会上面的反转链表,那就会这一题。想详细了解反转过程的可以看下这个视频,讲的很清楚。
代码
var reverseKGroup = function (head, k) {
let dummy = new ListNode(0, head)
let left = dummy, right = dummy
while (right.next) {
for (let i = 0; i < k && right; i++) {
right = right.next
}
if (!right) break
let start = left.next, end = right.next
right.next = null
left.next = reverseList(left.next)
start.next = end
left = right = start
}
return dummy.next
};
function reverseList(head) {
let prev = null, curr = head
while (curr) {
let next = curr.next
curr.next = prev
prev = curr
curr = next
}
return prev
}
11.随机链表的复制 ⭐
思路
解这题的思路是用一个哈希表维护一个原始节点到复制节点的映射,这样,当遍历原始链表时,可以在常数时间内找到其对应的复制节点,并建立正确的next和random关系。
代码
var copyRandomList = function (head) {
const map = new Map()
let curr = head
while (curr) {
map.set(curr, new Node(curr.val))
curr = curr.next
}
curr = head
while (curr) {
if (curr.next) {
map.get(curr).next = map.get(curr.next)
}
if (curr.random) {
map.get(curr).random = map.get(curr.random)
}
curr = curr.next
}
return map.get(head)
};
12.排序链表 ⭐
思路
这题一个暴力的方法就是将链表转成数组进行排序,然后再构建成链表。
代码
var sortList = function (head) {
const arr = []
while (head) {
arr.push(head.val)
head = head.next
}
arr.sort((a, b) => a - b)
let dummy = new ListNode(0)
let curr = dummy
for (let i = 0; i < arr.length; i++) {
curr.next = new ListNode(arr[i])
curr = curr.next
}
return dummy.next
};
13.合并 K 个升序链表 ⭐
思路
这题也可以跟上面一题一样,转成数组排序,然后构造链表。当然这也是暴力解法。
代码
var mergeKLists = function (lists) {
const arr = []
for (let list of lists) {
while (list) {
arr.push(list.val)
list = list.next
}
}
arr.sort((a, b) => a - b)
let dummy = new ListNode(0)
let curr = dummy
for (let i = 0; i < arr.length; i++) {
curr.next = new ListNode(arr[i])
curr = curr.next
}
return dummy.next
};
14.LRU 缓存 ⭐
思路
解这题可以使用JS中的Map,因为在JavaScript中,Map会根据插入的顺序保存键值对。所以在此场景中,它特别适合实现LRU缓存。
代码
var LRUCache = function (capacity) {
this.map = new Map()
this.size = capacity
};
LRUCache.prototype.get = function (key) {
const val = this.map.get(key)
if (this.map.has(key)) {
this.map.delete(key)
this.map.set(key, val)
return val
} else {
return -1
}
};
LRUCache.prototype.put = function (key, value) {
if (this.map.has(key)) {
this.map.delete(key)
}
this.map.set(key, value)
if (this.map.size > this.size) {
this.map.delete(this.map.keys().next().value)
}
};
热题100的链表部分的题目就全部完结了,我们发现解链表的题目大部分都是使用指针去解决,然后上面我有把链表转成数组解决,这种方法是不推荐的,我只是图个简单才用这种方法的,其实链表的题目只要理解了思路还是很简单的,真不行就转成数组解,然后再构造成链表,当然这并不是好的解法,只是没办法之下或者偷懒解法。