力扣刷题之链表(2) | 24. 两两交换链表中的节点、19. 删除链表的倒数第N个节点、面试题02.07. 链表相交、142.环形链表II

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

题目链接

思路

完成链表中的两两交换,不是仅仅将数值交换,而是完成结点的交换。这里建议使用虚拟头结点,会方便很多,要不然没有前一个指针指向头结点,还要单独处理。对于虚拟头结点,可以参考昨日Leetcode203. 移除链表元素的讲解。
在两两交换元素时,建议画图来理解。由于链表结构的特殊性,我们需要拿到被交换元素的前后两个元素来操作
初始时,我们进行如下三个步骤的操作,即可完成头两个结点交换。
在这里插入图片描述
完成头两个结点的交换后,链表如下:
在这里插入图片描述

难点

在两两交换链表中结点时,由于每组有两个结点。为了方便表述,这里分别用一号结点二号结点来表示原始链表中被交换的左、右两个结点。而被交换两结点后一位结点叫作三号结点

  • 当完成步骤1操作时(pre.next指向二号结点),此时一号结点发生断链,无法获取到一号结点。所以在步骤1之前,我们需要额外定义一个指针cur来存储一号结点
  • 当完成步骤2操作时,原本二号结点的next指向三号结点,此时三号结点发生断链,无法获取到三号结点。所以在步骤3之前,我们需要额外定义一个指针temp来存储三号结点
  • 完成三个步骤后,就完成了两两结点的交换。需要移动pre指针,由思路里的分析得到,pre指针需要在被交换两结点的前一个位置,所以每次完成交换后,pre需向后移动两个位置
  • 如何确定遍历链表时的终止条件?
    当链表元素是偶数时,终止条件为pre.next==null;当链表元素是奇数时,终止条件未pre.next.next==null。所以while()中填入pre.next!=null && pre.next.next!=null,两个条件不可以交换顺序,否则会出现空指针异常

代码(Java)

写法1:虚拟头结点

时间复杂度:O(n)
空间复杂度:O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyhead = new ListNode(0);
        dummyhead.next=head;
        ListNode pre = dummyhead;

        while (pre.next!=null && pre.next.next!=null){
            //当链表为偶数个结点时,pre.next为空则终;
            //当链表为奇数个结点时,pre.next.next为空则终止,即仅剩下一个未交换的结点;
            //两个条件不能交换位置,以免发生空指针异常
            
            ListNode cur = pre.next; //被交换的左结点(一号结点)
            ListNode temp = cur.next.next; //被交换两结点后的下一个结点(三号结点)
            pre.next=cur.next;///前结点的next指向二号结点,此时一号结点断开
            pre.next.next=cur;//二号结点的next指向一号结点,此时三号结点断开
            cur.next=temp;
            
            pre=pre.next.next;//移动pre结点
        }
        return dummyhead.next;
    }
}

写法2:递归

时间复杂度:O(n)
空间复杂度:O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        // base case 退出提交
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;

        return next;
    }
} 

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

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

题目链接

思路

这道题我们仍然采用虚拟头结点,此时无论删除哪个结点,都可以统一处理。
由于需要删除的是倒数第N个结点,我们需要用到双指针法,定义两个快慢指针,初始时都指向dummyhead快指针先走N步,然后快慢指针一起移动,直到快指针为null,此时慢指针落后快指针N步,也就拿到了要删除的结点。

难点

从前面链表的学习中,我们知道要想删除某个结点,需要拿到该结点的前一个结点,从而进行删除操作。所以让快指针先走N+1步,随后快慢指针一起向后移动,从而拿到删除结点的前一个结点。

  • 快慢指针初始化

在这里插入图片描述

  • 快指针先移动N+1个单位

在这里插入图片描述

  • 快慢指针一起移动,直到快指针为null,此时慢指针指向删除结点的前一个结点

在这里插入图片描述

  • 通过慢指针,完成对结点的删除操作。

在这里插入图片描述

代码(Java)

写法:双指针法

时间复杂度:O(n)
空间复杂度:O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyhead = new ListNode(0);
        dummyhead.next=head;
        ListNode fast = dummyhead;
        ListNode slow = dummyhead;

        for (int i=0;i<n+1;i++){
                fast=fast.next;
        }

        while (fast!=null){
            fast=fast.next;
            slow=slow.next;
        }

        slow.next=slow.next.next;
        return dummyhead.next;
    }
}

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

Leetcode 面试题 02.07. 链表相交

题目链接

思路

这道题需要求两个链表的共同结点,注意并不能理解为找到共同的结点值,而是求共同结点的指针。根据题意,我们可知一旦有共同结点的两链表,则从该结点开始到尾部都是共同结点
所以,在判断是否是共同结点前,我们可以先将两个链表的长度变为一致,再来判断是否为共同结点

代码(Java)

时间复杂度:O(n)
空间复杂度:O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA=0,lenB=0;
        ListNode curA=headA;
        ListNode curB=headB;
        
        while (curA!=null){//求得链表A的长度
            lenA++;
            curA=curA.next;
        }
        while (curB!=null){//求得链表B的长度
            lenB++;
            curB=curB.next;
        }
        
        //重置cur指针,使其指向链表头
        curA=headA;
        curB=headB;
        
        //让链表A为长度最长的链表,如果链表B长度大于链表A,则交换
        if (lenB>lenA){
            //交换长度
            int maxLen = lenB;
            lenB=lenA;
            lenA=maxLen;
            //交换链表
            ListNode temp = curB;
            curB=curA;
            curA=temp;
        }
        
        int margin = lenA-lenB;
        //舍去链表A多于链表B的部分,使两链表尾部对齐
        for (int i = margin; i>0 ; i--) {
            curA=curA.next;
        }
        while(curA!=null){
            if (curA==curB){
                return curA;
            }else {
                curA=curA.next;
                curB=curB.next;
            }
        }
        return null;
    }
}

代码随想录链接

下面是代码随想录的文章链接,帮助大家更好地理解这道题。
文章

Leetcode 142.环形链表II

题目链接

思路

这道题可以看作两个问题:

  1. 判断链表是否有环
  2. 如果有环,如何确定环的入口

难点

  • 判断链表是否有环

采用快慢指针的方法,分别定义快慢指针从头结点出发,快指针每次移动两个结点慢指针每次移动一个结点,则若有环,快慢指针则会相遇。
为什么有环时,快慢指针一定会相遇呢?是否存在快慢指针一直在环里移动,从而错个彼此? 因为快指针相对于慢指针每次多移动一个结点,可以看作慢指针静止,快指针每次以一个结点的速度在环内追逐慢指针,所以一定会相遇。

  • 如果有环,如何确定环的入口

在这里插入图片描述
因为快指针的移动速度是慢指针的两倍,
所以 2(x+y)=x+y+n(y+z),这里n代表快指针在环内转的圈数。
化简可得x=(n-1)(y+z)+z,当n>=1时,y+z都是环的长度(结点数),代表快指针在环内转的圈数,而落脚点都是在环入口结点,当n=1时,x=z代表的也就是相遇结点到环入口结点的距离🟰头结点到环入口结点的距离

注意:在慢指针走第一圈时,就和快指针相遇了。

首先慢指针进环时,快指针已经进环了。
下面考虑边界情况,假设慢指针在进环入口,快指针也在环入口,那么把这个环展开成直线,如下图所示:
在这里插入图片描述
那么快慢指针一定会在环入口3相遇,此时慢指针走了一圈,快指针走了两圈。
所以在慢指针走第一圈时,就和快指针相遇了。

代码(Java)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast!=null && fast.next!=null){//由于快指针每次移动两个结点
            fast=fast.next.next;
            slow=slow.next;
            if (fast==slow){//有环
                ListNode index1 = fast;//相遇点
                ListNode index2 = head;//头结点
                while(index1!=index2){
                    index1=index1.next;
                    index2=index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

总结

今天的内容较昨天来说,更进阶了一些,不仅考察对链表数据结构的基础操作,也需要对题目进行分析。特别是142.环形链表II,第一次写很困难,没有一点头绪,建议大家先看视频,最后写出来也要多花点时间理解,确定环和找环入口。另外使用了双指针法,多画图多分析,就很容易搞清楚两个指针分别的任务,循环的终止条件等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fuego91

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值