代码随想录算法训练营第四天丨 链表part 02

文章详细解析了链表操作中的节点交换、删除倒数第N个节点、链表相交以及环形链表II的问题,通过虚拟头节点和快慢指针的方法来解决,提供代码示例和解题思路分析。
摘要由CSDN通过智能技术生成

文档讲解:代码随想录

状态:已完成

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

思路

首先肯定是第一时间想到用虚拟头节点的方式的,因为这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。

对于交换结点时 我们需要特别注意交换的顺序,不然操作起来很容易乱。

初始时,cur指向虚拟头结点,然后进行如下三步:

操作之后,链表如下:

最后是这样:

代码如下:

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dumHead = new ListNode(-1,head);//设置一个虚拟头结点,并指向head
        ListNode current = dumHead;              //创建一个指针指向 虚拟头结点
        ListNode tempOne;        //临时结点,保存两个节点中第一个结点  
        ListNode tempTwo;       //临时结点,保存两个节点中的第二个结点
        while(current.next != null &&current.next.next != null){
            tempOne = current.next;
            tempTwo = current.next.next.next;

            current.next = current.next.next;//步骤一
            current.next.next = tempOne;//步骤二
            tempOne.next = tempTwo;//步骤三

            current = current.next.next;//current移动,准备下一轮的交换
        }
        return dumHead.next;
    }
}

在阅读上述代码时,一定要仔细看边上的注释,对于每行代码干啥的都写得很清楚。交换过程不清楚就看上述几张图片。

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

思路

还是使用虚拟头节点的方式

如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

定义fast指针和slow指针,初始值为虚拟头结点:

fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:

fast和slow同时移动,直到fast指向末尾,如题:

删除slow指向的下一个节点,如图:

代码如下:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dumHead = new ListNode(-1,head);//创建虚拟头节点并指向head
        ListNode fast = dumHead;//快指针
        ListNode slow = dumHead;//慢指针

        //实际循环次数为 n+1,因为需要快指针多走一步,那这样最后删除结点前,慢指针会指向删除结点的前驱
        for(;n >= 0 && fast != null;n--){
            fast = fast.next;   
        }
        //快慢指针同时移动,循环结束之后慢指针会指向删除结点的前驱
        while(fast !=null){
            fast = fast.next;
            slow = slow.next;
        }
        //删除结点
        slow.next = slow.next.next;
        return dumHead.next;
    }
}

理解该题需要理解上述代码和图片,需要仔细阅读

面试题 02.07. 链表相交

思路

其实刚开始是想着两层while循环嵌套直接查找的,但是后面看到别人说的一句“当两链表有交点时,那么他们交点及之后的长度是一定是一致的”,这一下整个思路清晰明了。

其实,简单来说,这道题目就是就是求两个链表交点节点的指针【交点不是数值相等,而是指针相等】

交点不是数值相等,而是指针相等

我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:

此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。

具体代码如下,需要据上图去理解下述代码

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //整体思路:当两链表有交点时,那么他们交点及之后的长度是一定是一致的
        int lenA = 0;//链表A的长度
        int lenB = 0;//链表B的长度
        //定义两个指针,一个指向链表A的head,一个指向B的head
        ListNode curA = headA;
        ListNode curB = headB;

        //下述两个while循环用来得出两链表的长度
        while(curA != null){
            curA = curA.next;
            lenA++;
        }
        while(curB != null){
            curB = curB.next;
            lenB++;
        }
        //重新指向头节点
        curA = headA;
        curB = headB;
        //算出两链表的长度差
        int num = lenA - lenB;
        
        if(num >= 0){//A的长度 大于等于 B的长度
            for(;num > 0;num--){
                curA = curA.next;
            }
            while(curA != null&&curB != null){
                if(curA == curB){
                    return curA;
                }
                curA = curA.next;
                curB = curB.next;
            }
        }else{//A的长度 小于 B的长度
            for(;num < 0;num++){
                curB = curB.next;
            }
            while(curB != null&&curB != null){
                if(curA == curB){
                    return curB;
                }
                curA = curA.next;
                curB = curB.next;
            }
        }
        return null;
    }
}

142.环形链表II

建议:在看该题思路之前最好把题解和卡哥的讲解视频看一遍

《代码随想录》算法视频公开课 (opens new window)把环形链表讲清楚!| LeetCode:142.环形链表II

视频连接:

思路

其实刚看这道题目时,我是很蒙的。无从下手,所以我就先看卡哥的视频去梳理整个的解题思路

这道题目,不仅考察对链表的操作,而且还需要一些数学运算。

主要考察两知识点:

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口
首先,是判断链表是否有环

就是使用快慢指针,定义 fast 和 slow 指针,一起从头结点出发,fast 每次移动 两个结点,slow 每次移动一个结点,如果两个指针在中途相遇了,就一定有环。

所以这里就有个问题了,为什么这两个指针在有环的情况下一定会相遇呢,这里我就用卡哥给的解释:

可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。

会发现最终都是这种情况, 如下图:

 

fast和slow各自再走一步, fast和slow就相遇了

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

动画如下:

如果有环,如何找到这个环的入口

到了此处就代表我们已经知道了该链表是有环的,那么如何找到这个环的入口呢?

这里我也用卡哥给的解释:

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

动画如下:

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

代码:

/**
 * 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 slow = head;
        ListNode fast = 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;
    }
}

 以上为我做题时候的相关思路,语言组织能力较弱,有错误望指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

叫一只啦啦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值