剑指 Offer(专项突击版)第21|22题

前言

  • 现在前端要求变高了,找工作的时可能会碰上算法题,每天刷几道算法题做足准备,今天是《剑指 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题的启发,可以用两个指针来判断链表中是否包含环。可以定义两个指针并同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果链表中不包含环,走得快的指针直到抵达链表的尾节点都不会和走得慢的指针相遇。如果链表中包含环,走得快的指针在环里绕了一圈之后将会追上走得慢的指针。因此,可以根据一快一慢两个指针是否能够相遇来判断链表中是否包含环。

推导:

  1. 设链表中环外部分的长度为 a,slow 指针进入环后,又走了b的距离与fast相遇,此时,fast 指针已经走完了环的n圈,因此:

a. fast走过的总距离为a + n(b + c) + b =>a + (n + 1)b + nc;

b. slow走过的总距离为a + b;

  1. 根据题意,任意时刻,fast 指针走过的距离都为slow指针的2倍,

则有a + (n + 1)b + nc = 2(a + b)→a = c + (n - 1)(b + c);

  1. 有了a = c + (n - 1)(b + c)的等量关系会发现:从相遇点到入环点的距离c,加上 n-1圈的环长,恰好等于从链表头部到入环点的距离;

  2. 因此,当发现 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大佬一起交流。

猜你喜欢

转载自blog.csdn.net/Likestarr/article/details/135360368