【代码随想录训练营】【Day04】第二章|链表|24. 两两交换链表中的节点|19.删除链表的倒数第N个节点|面试题 02.07. 链表相交|142.环形链表II|总结

24. 两两交换链表中的节点

题目详细:LeetCode.24

Java解法(递归):

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null)
            return head;
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
}

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

题目详细:LeetCode.19

Java解法(双指针(快慢指针)):

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode ans = new ListNode(-1, head), p_del = ans, p_last = ans;
        while(0 < n--){
            p_last = p_last.next;
        }
        while(p_last.next != null){
            p_del = p_del.next;
            p_last = p_last.next;
        }
        p_del.next = p_del.next.next;
        return ans.next;
    }
}

面试题 02.07. 链表相交

题目详细:LeetCode.面试题 02.07.

这道题算是很常见的面试题了,难度也比较简单,当看到查找两个链表相交的节点时,我第一个蹦出来的解题方法就是利用哈希表,只要遍历一个链表并用哈希表进行记录,再遍历另一个链表,看看哈希表中是否已存在同样的记录,如果出现相同记录,则该记录的节点为两个链表的公共交点。

Java解法(哈希表):

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        HashSet<ListNode> set = new HashSet<>();
        while(headA != null){
            set.add(headA);
            headA = headA.next;
        }
        while(headB != null){
            if(!set.add(headB)){
                return headB;
            }
            headB = headB.next;
        }
        return null;
    }
}

这道题也可以使用队列来解决,但它们两的解决思路是殊途同归的,时间复杂度和空间复杂度都是 O(n+m),于是我猜想能不能利用双指针实现空间复杂度为 O(1) 的算法。

但是才学疏浅的我虽然知道可以利用双指针来解决,但是却不知道如何实现,看了题解之后才大彻大悟:

在这里分享一个来自LeetCode玩家的题解:【图解】利用双指针解题

Java解法(双指针):

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pA = headA, pB = headB;
        while(pA != pB){
            pA = pA != null ? pA.next : headB;
            pB = pB != null ? pB.next : headA;
        }
        return pA;
    }
}

142.环形链表II

题目详细:LeetCode.142

题目要求找出环形链表中环的入口节点,这和上一题找出两个链表的相交节点有一曲同工之妙,也是可以利用哈希表来记录已经遍历过的节点:

  • 特判:当链表为空或者链表中只有一个节点时,不属于环形链表,直接返回null。
  • 当链表中有环时,只要一直遍历链表并且利用哈希表进行记录,直到找到重复记录的节点并返回即可;
  • 当链表中没有环时,在链表遍历结束后,用于遍历链表的指针head将指向null。

Java解法(哈希表):

public class Solution {
    public ListNode detectCycle(ListNode head) {
        // 链表为空或链表中没有环
        if(head == null || head.next == null){
            return null;
        }
        // 使用哈希表记录遍历过的节点
        HashSet<ListNode> set = new HashSet<>();
        // 当链表中有环时,一定能找到重复记录的节点
        while(head != null){
            if(!set.add(head)){
                // 重复记录的节点则为环的入口节点
                return head;
            }
            head = head.next;
        }
        // 当链表中无环时,链表遍历结束后head指向null
        return head;
    }
}

如果不画图的话,还真难发现这道题也可以利用双指针来解决,通过定义快慢两个指针并构建两轮相遇的过程,来找出环的入口节点:

  • 构建第一轮相遇:
    • 如果快指针为null,则说明链表不属于环形链表或链表为空
    • 如果快慢两个指针在第一轮遍历过程中,能够相遇,则能够确定该链表存在环,但不能确定相遇的节点是否为环的入口节点,还需要构建第二轮相遇来找出环的入口节点
  • 构建第二轮相遇:
    • 将快指针重新赋值,使其指向链表的头节点,此时我们可以发现,快慢指针分别指向的两个节点,它们到达环的入口节点的距离恰好是相等的
    • 所以同时遍历快慢两个指针,直到两者再次相遇,那么相遇的节点则为环的入口节点

但是我觉得我不能很好地描述解题的思路和过程,所以这里贴出LeetCode玩家的题解,它以画图的形式来解释快慢指针两轮相遇的过程,我觉得更加全面易懂:【图解】利用双指针解题

Java解法(双指针):

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
        // 构建第一轮相遇
        while(true){
            if(fast == null || fast.next == null){
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
            // 如果双指针第一轮能够相遇则确定该链表存在环,但不能确定相遇的节点是环的入口节点
            if(slow == fast) break;
        }
        fast = head;
        // 到了这一步,此时我们可以发现,快慢指针分别指向的两个节点,它们到达环的入口节点的距离恰好是相等的
        // 所以同时遍历快慢两个指针,直到两者再次相遇,那么相遇的节点则为环的入口节点
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return fast;
    }
}

总结

经过多次练习链表相关的题目后,发现在解题过程中,经常定义一个虚拟头节点并连接表头后,再对链表进行后续的操作,所以这里总结一下虚拟头节点的作用:

【有空再总结】


先仅作题打卡,解析后续有空再补上,每逢佳节实在是太忙碌了,但是尽管再忙,时间再少,也希望每天都能做到“今日事今日毕”,所以用此诗作结吧:

世人若被明日累, 春去秋来老将至。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值