代码随想录算法训练营day05 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II

一、LeeCode 24 两两交换链表中的节点


 题目链接:24. 两两交换链表中的节点

 文档讲解:来自代码随想录

 视频讲解:卡子哥视频讲解系列——两两交换链表中的节点


思路:使用虚拟头结点会方便很多(图片截取自代码随想录

创建cur指针指向虚拟头结点,然后进行四步操作:

步骤一:cur 的 next 指针指向值为2的节点;

步骤二:值为2的节点的 next 指向值为1的节点;

步骤三:值为1的节点的 next 指向值为3的节点;

步骤四:cur向下移动两个节点,执行前三步;

(注意:需要用临时节点 tmp 和 tmp1,先保存值为1的节点和值为3的节点,然后再执行四步骤。否则先执行完步骤一,就无法再找到值为1的节点了;同理,先执行完步骤二,就无法再找到值为3的节点了。)

操作之后,链表如下:

 更直观的将链表展开:

(在代码中会给出我理解的尽可能详细的注释)

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);  // 创建虚拟头节点
        dummyHead->next = head;                 // 虚拟头节点指向头节点
        ListNode* cur = dummyHead;              // 创建cur初始指向虚拟头结点
        // 链表如果有偶数个节点,则cur结束指向最后一个节点,此时cur->next = NULL
        // 链表如果有奇数个节点,则cur结束指向倒数第二个节点,此时cur->next->next = NULL
        while (cur->next != NULL && cur->next->next != NULL) {
            ListNode* tmp = cur->next;              // 临时存放cur->next
            ListNode* tmp1 = cur->next->next->next; // 临时存放cur->next->next->next

            // 三步走交换相邻节点
            cur->next = cur->next->next;    // cur直接指向2的节点
            cur->next->next = tmp;          // 2的节点指向1的节点
            tmp->next = tmp1;               // 1的节点指向3的节点

            cur = cur->next->next;  // cur向后移两位
        }
        return dummyHead->next;     // 返回新的链表的头节点
        delete dummyHead;
    }
};

二、LeeCode 19 删除链表的倒数第N个节点


 题目链接:19. 删除链表的倒数第N个节点

 文档讲解:来自代码随想录

 视频讲解:卡子哥视频讲解系列——删除链表倒数第N个节点


思路:跟随卡子哥的指引,采用双指针法。

创建 fast 和 slow 快慢指针初始都指向虚拟头节点

让 fast 先向后移动 N 个节点,再让 fast 和 slow 一起移动,直到 fast 指向链表最后一个节点

此时,slow 指向倒数第 N+1 个节点(要想操作一个节点,需让 cur 指向它的前一个节点,然后用cur->next 来操作该节点)

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);  // 建立虚拟头节点
        dummyHead->next = head;     // 让虚拟头节点指向head

        // 建立快、慢指针
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;

        // 快指针往后移动 n 步
        while (n-- && fast != NULL) {
            fast = fast->next;
        }

        // 然后,快慢指针一起向后移动,到快指针指向链表的最后一个节点为止
        while (fast->next != NULL) {
            fast = fast->next;
            slow = slow->next;
        }

        // 此时慢指针指向倒数第 N+1 个节点
        // 这样就可以操作 slow->next 来移除倒数第 N 个节点了
        ListNode* tmp = slow->next;
        slow->next = slow->next->next;
        delete tmp;

        return dummyHead->next;     // 返回链表的头节点
        delete dummyHead;
    }
};

三、LeeCode 面试题02.07 链表相交


 题目链接:面试题 02.07. 链表相交

 文档讲解:来自代码随想录


思路:首先明确,判断两个链表有没有相交节点,是比较两个节点的地址是不是相等。题目的意思是,已知给你提供的两个链表要么有交点,要么没有。然后我们通过比较两个链表有没有相同地址的节点,从而判断它们相不相交。

截图来自代码随想录

创建curA指向链表A的头结点,curB指向链表B的头结点:

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

此时就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0; // 分别记录链表A和B的长度

        // 求链表A的长度
        while (curA != NULL) {
            lenA++;
            curA = curA->next;
        }
        // 求链表B的长度
        while (curB != NULL) {
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap(lenA, lenB);
            swap(curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA和curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

四、LeeCode 142 环形链表II


 题目链接:142. 环形链表 II

 文档讲解:来自代码随想录

 视频讲解:卡子哥视频讲解系列——环形链表


这道题非常值得反复温习

这里直接放上卡子哥讲解的思路(自己没思路,直接看的卡子哥)

思路:这道题涉及两问

  1. 判断链表是否环
  2. 如果有环,如何找到这个环的入口

判断链表是否有环:

        使用快慢指针,快指针每次向后移动两步,慢指针每次向后移动一步。如果链表存在环,则快慢指针一定会相遇。

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

假设从头节点到环的入口节点的节点数为 x,环形入口点到快慢指针相遇点的节点数为 y,从相遇点再到环形入口点的节点数为 z,如图所示(截图来自代码随想录

重点在于找到 x,y,z三者的关系

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

由fast指针速度是slow指针速度的两倍可知,在相遇时fast走过的节点数应该为slow走过的节点数的两倍:(x+y)*2 = x+y+n(y+z)

我们关注的就是 x 如何表示(因为 x 是头结点到入口节点的节点数,知道 x 就找到入口了)

变式得:x = n(y+z) - y = (n-1)(y+z) + z

以 n=1 时举例分析,此时 x = z,表示如果有两个指针都以1的速度,分别从头结点和相遇点出发,那么它们会在入口节点处相遇;当 n>1 时也是一样的情况,相遇点处出发的指针会在环内绕(n-1)圈,然后再入口节点处相遇。

因此我们在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

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

动图截取自代码随想录:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0; // 分别记录链表A和B的长度

        // 求链表A的长度
        while (curA != NULL) {
            lenA++;
            curA = curA->next;
        }
        // 求链表B的长度
        while (curB != NULL) {
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap(lenA, lenB);
            swap(curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA和curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值