✅✅代码随想录算法训练营Day4 | | 24. 两两交换链表中的节点 ,19.删除链表的倒数第N个节点 , 面试题 02.07. 链表相交

1,403 阅读5分钟

✅✅代码随想录算法训练营Day4 | | 24. 两两交换链表中的节点 ,19.删除链表的倒数第N个节点 , 面试题 02.07. 链表相交

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第8篇文章 点击查看文章详情 🚀🚀

24. 两两交换链表中的节点 - 力扣(LeetCode)

难点:🔥🔥🔥

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。
你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。


输入: head = [1,2,3,4]
输出: [2,1,4,3]

交换链表

    if(!head) return head;
    let dummy = new ListNode(0,head);
    let cur = dummy;
    while(cur.next && cur.next.next){
        let temp1 = cur.next;
        let temp2 = cur.next.next.next;
        cur.next = cur.next.next;
        cur.next.next = temp1;
        temp1.next = temp2;
        cur = cur.next.next;
    }
    return dummy.next;

难点

两两交换链表和翻转链表

一上来审题,发现两两交换,好,这不就是小范围的翻转链表吗?

这有啥难?

一顿操作猛如虎,仔细一看原地杵~

❌❌❌

反转链表

image.png

反转的操作是连贯的,我们刚开始设置的pre指针可以随着cur的遍历一直来到尾节点。然后直接将尾节点作为头节点遍历,就实现反转链表的操作了。

翻转链表

image.png

反转的操作是小范围,断断续续的,故思路肯定不会和反转链表一致,所以我们要从头节点处想办法

实现的细节

要实现两两交换的效果,我们也可以从反转链表中找到思路

找上一个节点来实现

(在头部节点时即创建一个虚拟头节点

四个节点

在对 12 节点进行两两交换的过程中,实际上和四个节点有关

虚拟头节点,1节点,2节点,3节点

注意

在这里我们因为首先要将虚拟头节点指向2节点

 cur.next = cur.next.next;

会直接改变原链表中的指向,为了能够正常取到1节点3节点 ,可以先将其保存起来

   let temp1 = cur.next;
   let temp2 = cur.next.next.next;

如何实现两两交换?

在看到两两交换时,我下意识想到的是创建一个能够实现两两交换的函数,因为这个操作是重复的。并且要实现两两操作,尝试了一下用count去计数判断。

🙅🙅🙅

要命~

哪有那么复杂,慢慢遍历过去不就好了

    while(cur.next && cur.next.next){
    
    
    }

还能处理链表长度为奇数的时候,不进入循环,直接返回

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

难度: 🔥 🔥

给你一个链表,删除链表的倒数第 n个结点,并且返回链表的头结点。


输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]

快慢指针

    if(!head) return head;
    let dummy = new ListNode(0,head);
    let fast = dummy;
    let slow = dummy;
    while(n--){
        fast = fast.next;
    }
    while(fast.next){
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return dummy.next;

感觉这种写法有点像固定公式,直接给我形成肌肉记忆了。

难点

最后改变指向时

自己写的时候是

slow.next = fast;

但最后运行时发现,一般情况下是可以,但要是删除的是头结点就寄了~

面试题 02.07. 链表相交 - 力扣(LeetCode)

难度:🔥🔥🔥

image.png

找到相同的位置开始遍历

const getlen = (head) => {
    let len = 0
    while(head){
        head = head.next;
        len++;
    }
    return len;
}
var getIntersectionNode = function(headA, headB) {
    let curA = headA;
    let curB = headB;
    let lenA = getlen(headA)
    let lenB = getlen(headB)

    if(lenA > lenB){
        [lenB,lenA] = [lenA,lenB];
        [curB,curA] = [curA,curB];
    }

    len = lenB - lenA;
    while(len--){
        curB = curB.next;
    }
    console.log(curB)
    while(curB && curB != curA){
        curA = curA.next;
        curB = curB.next;
    }
    if(curB == curA){
        return curB
    }

};

难点

交换两个数组

 // 下面交换变量注意加 “分号” ,两个数组交换变量在同一个作用域下时
 // 如果不加分号,下面两条代码等同于一条代码
     : [curA, curB] = [lenB, lenA]
     
        [curA, curB] = [curB, curA];
        [lenA, lenB] = [lenB, lenA];

142. 环形链表 II - 力扣(LeetCode)

难度:🔥🔥

image.png

环形链表这类题我没用卡哥的思路,在这里我用的是修言老师小册的思路~

修言老师的思路

何为链表成环?

抛开那些看起来复杂的高大上的解法,让我们回归本质。

👇 👇 👇

假如现实中有一个长跑爱好者李雷,这货很狂,他立了一个 flag,说要徒步环游世界:

image.png

地球的周长围出来的这个圆,它就是一个“环”。李雷现在就想围着这个环跑上一圈,说他狂,他也没那么狂——他觉得自己最多跑一圈,为了防止自己跑过界,他决定在出发的地方立一个 flag

image.png

这样,不管李雷走完这个环用了多少年,世事如何变迁,只要他的 flag 还没有倒,那么李雷就一定能回到自己梦开始的地方:)。
换个角度看:只要李雷在闷头前进的过程中,发现了 flag 的存在,那么就意味着,李雷确实走了一个环。毕竟若这是一条线,他将永远无法回到起点。

回到链表的世界里,也是一个道理。一个环形链表的基本修养,是能够让遍历它的游标回到原点:

image.png

从 flag 出发,只要我能够再回到 flag 处,那么就意味着,我正在遍历一个环形链表。

这样像这类问题就变简单了~

var detectCycle = function(head) {
    if(!head) return head;
    let cur = head;
    // 设置flag
    cur.flag = 0;
    while(cur){
        // 如果该flag存在,说明这个地方之前已经来过了  => 成环!!
        if(cur.flag){
            return cur;
        }
        // 没来过,就打上记号
        cur.flag = 1;
        cur = cur.next;
    }
    return null;
};

收获

因为链表的题目在半个月前已经刷过一次了,所以这次刷起来还是比较快的。

要论收获的话,还是对链表一些更细节的操作有了更多的了解。

  1. 这次因为算是不会再把两两交换链表和翻转链表搞混了,之前以为这两哥们都是一种解法。。。
  2. 删除节点还是按照删除的节点去做,换汤不换药
  3. 链表的相交,主要对链表的操作更加熟练。再碰到类似流程比较多的题目时不会畏惧了
  4. 环形链表直接采用比较简单,也比较好理解的方式~

期待明天的任务啦~

⭐⭐⭐