算法训练 | 链表Part2 | 24.两两交换链表中的节点、19.删除链表的倒数第N个节点、106.链表相交、142.环形链表II

目录

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

指向改变

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

快慢指针法

106. 链表相交

指针对齐法

142.环形链表II

快慢指针法


嵌入式学习分享个人主页:Orion嵌入式随想录 - 小红书 (xiaohongshu.com)

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

指向改变
  • 解题思路

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

  • 解题步骤

    • 设置虚拟头节点

    • 设置cur操作指针开启循环,每次操作指针指向待交换两节点的前一个节点。故偶数cur->next != nullptr,奇数cur->next->next != nullptr

    • 需要断开并重新连接1节点和3节点。故需要临时保存1节点位置cur->next,3节点位置cur->next->next->next;

    • 执行三个步骤,0连2,2连1,1连3

    • 操作指针移动两位,指向下一组待交换两节点的前一个节点

    • 返回头节点,释放虚拟节点

  • 代码注意

    • 在执行转换步骤时,前一步已经连上,下一个节点直接用next找位置,找的为之前保存的位置

  • 代码一:虚拟头节点

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr) {
            ListNode* tmp = cur->next; // 记录临时节点
            ListNode* tmp1 = cur->next->next->next; // 记录临时节点

            cur->next = cur->next->next;    // 步骤一
            cur->next->next = tmp;          // 步骤二
            cur->next->next->next = tmp1;   // 步骤三

            cur = cur->next->next; // cur移动两位,准备下一轮交换
        }
        ListNode* result = dummyHead->next;
        delete dummyHead;
        return result;
    }
};

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

快慢指针法
  • 解题思路

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

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

    • fast首先走n + 1步 ,同时移动时slow才能指向删除节点的上一个节点(方便做删除操作,相当于多倒数一个,n+1)

    • fast和slow同时移动,直到fast指向末尾

    • 删除slow指向的下一个节点

  • 解题步骤

    • 创建虚拟头节点

    • 定义快慢指针

    • 设定快指针先走的长度n+1,循环快指针移动

    • 快慢指针同时移动,直到fast为空,slow到目标节点的前一个节点

    • 删除目标节点

    • 返回头节点

  • 代码注意

    • while(n-- && fast != NULL)要对fast的next处理 避免操作空指针,循环内都应该判断

  • 代码一:快慢指针

// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next; 
        
        // ListNode *tmp = slow->next;  C++释放内存的逻辑
        // slow->next = tmp->next;
        // delete tmp;
        
        return dummyHead->next;
    }
};

106. 链表相交

指针对齐法
  • 解题思路

    • 目前curA指向链表A的头结点,curB指向链表B的头结点

    • 求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置
    • 此时比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。

  • 解题步骤

    • 初始化操作指针为两个头节点

    • 求两个链表长度,并重新初始化操作指针

    • 计算长度差,移动长链表的操作指针,末尾对其

    • 循环内更新,找到则返回结束

  • 代码注意

    • 涉及指针操作注意要判断是否为空

    • 进入循环先判断是否相等再更新,否则会丢失刚对其就是相交的情况

  • 代码一:指针对齐

// 时间复杂度:O(n + m)
// 空间复杂度:O(1)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) { // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) { // 求链表B的长度
            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;
    }
};

142.环形链表II

快慢指针法
  • 解题思路

    • 可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

    • fast 走两个节点,slow走一个节点,其实相对于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 = n (y + z) - y ,再从n(y+z)中提出一个(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z (注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。当 n为1的时候,公式就化解为 x = z

    • 在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。让index1和index2同时移动,每次移动一个节点, 那相遇的地方就是环形入口的节点。(n如果大于1,index1指针在环里多转了(n-1)圈再遇到index2,相遇点依然是环形的入口节点。)

  • 解题步骤

    • 定义快慢指针指向头节点

    • 外循环找相遇,判断fast != NULL && fast->next != NULL因为fast快且两步走

    • 更新快慢指针

    • 找到相遇位置放index1,头节点index12

    • 内循环找入口,判断index1 != index2

    • 更新索引位置

    • 索引相等跳出循环,返回相同索引位置

  • 代码注意

    • 进循环先更新位置才可以产生相遇并进行判断

    • 在大循环内快慢节点前进的时候,每一次及时判断相遇位置,同时找入口并返回终止循环。不可以写在大循环外,不然造成资源浪费无止境加。

代码一:快慢指针

// 时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
// 空间复杂度: O(1)
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};

 (说明:基于代码随想录课程学习,部分内容引用自代码随想录文章)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值