双指针技巧秒杀七道链表题目(Javascript)

141.环形链表

题目链接

问题描述

在这里插入图片描述

解决方案

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    let slow = head, fast = head
    while(fast != null && fast.next != null) {
        slow = slow.next 
        fast = fast.next.next
        if(slow == fast) {
            return true
        }
    }
    return false
};

对于快慢指针,如果存在环,快慢指针一定会相遇

142. 环形链表II

题目链接

题目描述

在这里插入图片描述

解决方案

  • 基于环形链表I的解法,直观地来说就是当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
  • **原理:**我们假设快慢指针相遇时,慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步:
    在这里插入图片描述
  • fast 一定比 slow 多走了 k 步,这多走的 k 步其实就是 fast 指针在环里转圈圈,所以 k 的值就是环长度的「整数倍」。
  • 假设相遇点距环的起点的距离为 m,那么结合上图的 slow 指针,环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点。
  • 巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点:
    在这里插入图片描述
  • 所以,只要我们把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后一定会相遇,相遇之处就是环的起点了。
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    let slow = head, fast = head 
    while(fast != null && fast.next != null) {
        slow = slow.next 
        fast = fast.next.next
        if(slow == fast) break
    }
    // 考虑 fast遇到空指针没有环的情况
    if(fast == null || fast.next == null) {
        return null
    }
    // 将慢指针重新指向头结点
    slow = head
    // 快慢指针同步前进,相交点就是环起点
    while(slow != fast) {
        slow = slow.next
        fast = fast.next
    }
    return slow 
};

160.相交链表

题目链接

题目描述

在这里插入图片描述

解决方案

  • 此题难点在于,两条链表的长度可能不同,两条链表之间的节点无法对应
    在这里插入图片描述
  • 如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。
    如果用两个指针 p1 和 p2 分别在两条链表上前进,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1:
    在这里插入图片描述

先计算两条链表的长度,然后让 p1 和 p2 距离链表尾部的距离相同,然后齐头并进,

①解法

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    let p1 = headA, p2 = headB
    while(p1 != p2) {
        // p1走一步,如果走到A链表末尾,转到B链表
        if(p1 == null) {
            p1 = headB
        }else {
            p1 = p1.next
        }
        if(p2 == null) {
            p2 = headA
        }else {
            p2 = p2.next
        }
    }
    return p1
};

19. 删除链表的倒数第N个节点

题目链接

题目描述

在这里插入图片描述

解决方案

  • 要删除倒数第 n 个节点,就得获得倒数第 n + 1 个节点的引用。
  • 获取单链表的倒数第 k 个节点,就是想考察 双指针技巧 中快慢指针的运用,一般都会要求你只遍历一次链表,就算出倒数第 k 个节点。
  • 第一步,我们先让一个指针 p1 指向链表的头节点 head,然后走 k 步:
    在这里插入图片描述
  • 第二步,用一个指针 p2 指向链表头节点 head:
    在这里插入图片描述
  • 第三步,让 p1 和 p2 同时向前走,p1 走到链表末尾的空指针时走了 n - k 步,p2 也走了 n - k 步,也就是链表的倒数第 k 个节点:
    在这里插入图片描述
  • 这样,只遍历了一次链表,就获得了倒数第 k 个节点 p2
  • 解法中在链表头部接一个虚拟节点 dummy 是为了避免删除倒数第一个元素时出现空指针异常,在头部加入 dummy 节点并不影响尾部倒数第 k 个元素是什么。
/**
 * 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 dummy = new ListNode(-1)
    dummy.next = head
    // 删除倒数第n个节点,要先找到倒数第n+1个节点
    let x = findFromEnd(dummy, n+1)
    // 链表操作,删除n+1个节点
    x.next = x.next.next
    return dummy.next
};
// 找到链表的倒数第k个节点
var findFromEnd = function(head, k) {
    let p1 = head 
    // 只有一个元素的时候,返回 []
    if(p1.next == null) {
        return []
    }
    // p1先走k步
    for(let i = 0; i < k; i++) {
        p1 = p1.next
    }
    let p2 = head 
    // p1和p2 同时走n-k步
    while(p1 != null){
        p1 = p1.next 
        p2 = p2.next
    }
    // p2现在指向第n-k个节点 也就是倒数第k个节点
    return p2 
}

21. 合并两个有序列表

题目链接

题目描述

在这里插入图片描述

解决方案

  • 经典算法题:使用双指针技巧
    在这里插入图片描述
  • 这个算法的逻辑类似于「拉拉链」,l1, l2 类似于拉链两侧的锯齿,指针 p 就好像拉链的拉索,将两个有序链表合并。
  • 代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,
    也就是 dummy 节点,它相当于是个占位符,可以避免处理空指针的情况
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeTwoLists = function(list1, list2) {
    // 双指针技巧
    let dummy = new ListNode(-1), p = dummy
    let p1 = list1, p2 = list2
    while(p1 != null && p2 != null) {
        // 比较p1和p2两个指针所指向的内容的大小
        if(p1.val > p2.val) {
            p.next = p2
            p2 = p2.next
        }else {
            p.next = p1
            p1 = p1.next
        }
        // p指针需保证不断前进操作
        p = p.next
    }
    if(p1 != null) {
        p.next = p1
    }
    if(p2 != null) {
        p.next = p2
    }
    return dummy.next
};

23.合并K个升序链表

题目链接

问题描述在这里插入图片描述

解决方案(Java)

**利用优先级队列(二叉堆)**进行节点排序即可

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        // 虚拟头结点
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        // 优先级队列,最小堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>(
            lists.length, (a, b)->(a.val - b.val));
        // 将 k 个链表的头结点加入最小堆
        for (ListNode head : lists) {
            if (head != null)
                pq.add(head);
        }

        while (!pq.isEmpty()) {
            // 获取最小节点,接到结果链表中
            ListNode node = pq.poll();
            p.next = node;
            if (node.next != null) {
                pq.add(node.next);
            }
            // p 指针不断前进
            p = p.next;
        }
        return dummy.next;
    }
}
// 详细解析参见:
// https://labuladong.github.io/article/?qno=23

86.分隔链表

题目链接

题目描述

在这里插入图片描述

解决方案

自定义出两条全新的链表来获取小于x和大于等于x的节点,最后将两条链表链接起来即可

/**
 * 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} x
 * @return {ListNode}
 */
var partition = function(head, x) {
    // 分别用两条链表来进行获取内容,最后进行简单拼接即可
    let dummy1 = new ListNode(-1), p1 = dummy1
    let dummy2 = new ListNode(-1), p2 = dummy2
    let p = head
    // 通过p指针来比较每个节点应该放置在哪一条链子上
    while(p != null) {
        // >= 需要考虑相等的结果,相等的时候也放在第二条链子 p2上面
        if(p.val >= x) {
            p2.next = p
            p2 = p2.next
        }else {
            p1.next = p
            p1 = p1.next
        }
        // 要保持原链表,所以每个节点遍历比较完以后应该断开
        let temp = p.next
        p.next = null
        p = temp
    }
    p1.next = dummy2.next
    return dummy1.next
};

876.链表的中间节点

题目链接

问题描述

在这里插入图片描述

解决方案

直接使用快慢指针的写法进行求解即可

/**
 * 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 middleNode = function(head) {
    let slow = head, fast = head
    while(fast != null && fast.next != null) {
        slow = slow.next 
        fast = fast.next.next
    }
    return slow
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值