前言
-
现在前端要求变高了,找工作的时可能会碰上算法题,每天刷几道算法题做足准备,今天是《剑指 Offer(专项突击版)》第21|22题。
剑指 Offer II 021. 删除链表的倒数第 n 个结点
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
难度:中等
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1 输出:[]
示例 3:
输入:head = [1,2], n = 1 输出:[1]
提示:
● 链表中结点的数目为 sz
● 1 <= sz <= 30
● 0 <= Node.val <= 100
● 1 <= n <= sz
示例 1:
知识点: 链表[1] 双指针[2]
方法一:双指针
如果可以遍历链表两次,那么这个问题就会变得简单一些。在第1次遍历链表时,可以得出链表的节点总数n。在第2次遍历链表时,可以找出链表的第n-k个节点(即倒数第k+1个节点)。然后把倒数第k+1个节点的next指针指向倒数第k-1个节点,这样就可以把倒数第k个节点从链表中删除。
如果只能遍历链表一次。为了实现只遍历链表一次就能找到倒数第k+1个节点,可以定义两个指针。第1个指针P1从链表的头节点开始向前走k步,第2个指针P2保持不动;从第k+1步开始指针P2也从链表的头节点开始和指针P1以相同的速度遍历。由于两个指针的距离始终保持为k,当指针P1指向链表的尾节点时指针P2正好指向倒数第k+1个节点。
* 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} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let dmy = new ListNode(0, head);
let slow = dmy;
let fast = dmy;
while (n > 0) {
fast = fast.next;
n--;
}
while (fast !== null && fast.next !== null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dmy.next;
};
复杂度分析
-
时间复杂度:O(L),其中 L 是链表的长度。
-
空间复杂度:O(1))。
剑指 Offer II 022. 链表中环的入口节点
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意, pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明: 不允许修改给定的链表。
难度:中等
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
提示:
-
链表中节点的数目范围在范围 [0, 104] 内
-
-105 <= Node.val <= 105
-
pos 的值为 -1 或者链表中的一个有效索引
知识点: 哈希表[3] 链表[4] 双指针[5]
方法一:双指针
如果一个链表中没有环,那么自然不存在环的入口节点,此时应该返回null。
受21题的启发,可以用两个指针来判断链表中是否包含环。可以定义两个指针并同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果链表中不包含环,走得快的指针直到抵达链表的尾节点都不会和走得慢的指针相遇。如果链表中包含环,走得快的指针在环里绕了一圈之后将会追上走得慢的指针。因此,可以根据一快一慢两个指针是否能够相遇来判断链表中是否包含环。
推导:
-
设链表中环外部分的长度为 a,slow 指针进入环后,又走了b的距离与fast相遇,此时,fast 指针已经走完了环的n圈,因此:
a. fast走过的总距离为a + n(b + c) + b =>a + (n + 1)b + nc;
b. slow走过的总距离为a + b;
-
根据题意,任意时刻,fast 指针走过的距离都为slow指针的2倍,
则有a + (n + 1)b + nc = 2(a + b)→a = c + (n - 1)(b + c);
-
有了a = c + (n - 1)(b + c)的等量关系会发现:从相遇点到入环点的距离c,加上 n-1圈的环长,恰好等于从链表头部到入环点的距离;
-
因此,当发现 slow 与fast 相遇时,再额外使用一个指针ptr起始指向链表头部,随后 ptr和 slow 每次向后移动一个位置,最终它们会在入环点相遇;
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
if (head === null || head.next === null) {
return null;
}
let slow = head;
let fast = head;
while (fast !== null) {
slow = slow.next;
if (fast.next === null) {
return null;
}
fast = fast.next.next;
if (fast === slow) {
let ptr = head;
while (ptr !== slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
};
复杂度分析
-
时间复杂度:O(N),其中 N 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(N)+O(N)=O(N)
-
空间复杂度:O(1)。使用了 slow,fast,ptr 三个指针。
so
-
结尾依旧:长风破浪会有时,直挂云帆济沧海!
-
在线整理的确实很好,对文章进行了一个汇总整理,在线备战面试、刷题指南,拿走不谢,要学会站在别人的肩膀上提升自己点击这里-->
最后:
如果你现在正在找工作,可以私信“web”或者直接添加小助理进群领取前端面试小册、简历优化修改、大厂内推以及更多阿里、字节大厂面试真题合集,和p8大佬一起交流。