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

文章目录

  • 24.两两交换链表中的结点
    • 主要思路
    • cpp代码
  • 19.删除链表的倒数第N个结点
    • 主要思路
    • CPP代码
  • 面试题 02.07. 链表相交
    • 基本思路
    • CPP代码
  • 142.环形链表II
    • 主要思路
    • CPP代码

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

力扣题目链接

文章链接:24.两两交换链表中的结点

视频链接:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点

状态:第一次提交报错RE,主要原因在于循环条件没把握好。对空指针尽兴了操作。
循环条件必须是逻辑&&,而不是||,因为循环体内涉及到了node.next.next.next。

主要思路

画一个图,很容易能发现,cur结点必须在待交换的两个结点之前,并且我们还需要一个temp结点保存待交换两个结点之后的一个结点。如果我们想交换相邻两个结点,还需要第二个temp1来保存其中一个结点位置来完成交换。

交换逻辑如下:cur在虚拟头结点位置,首先让cur指向结点2,然后让2指向结点1,最后让结点1指向结点3。

综上所属,我们需要设计变量:虚拟头结点、当前结点、暂存结点1、暂存结点2.

然后就是遍历结点时的循环条件一定要注意,cur结点的下一个结点和下下个结点都不能为空,不然就会访问一个空指针的成员,肯定会报错。

cpp代码

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移动两位,准备下一轮交换
        }
        return dummyHead->next;
    }
};

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

力扣题目链接

文章链接:19.删除链表的倒数第N个结点

视频链接:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点

状态:毫无思路,只想到了先遍历一次链表,拿到size,用size-1的暴力解决办法;那么一趟扫描如何找到倒数第N个结点呢?答案就是双指针大法!构造速度差

主要思路

本题我觉得最重要的就是一趟扫描的基础上找到倒数第N个结点。

  • 双指针大法

我们设置快慢两个指针,快指针比慢指针领先n个,如果等到快指针指向链尾的NULL,就说明慢指针已经指向了倒数第N个结点。

  • 关于删除结点

想要删除某个节点,我们的当前结点一定要在待删除的结点之前,这样才能顺利完成删除(画图即可理解)。所以有两种处理办法,一种是再设置一个cur结点,让它始终在slow前面,另外一种就是让fast再比slow多跑一步,让slow结点指向删除结点的上一个结点。这样的话对fast的操作就比较讲究,在遍历fast的时候,注意while中的条件,再一个就是让fast多跑一步。

  • 是否设置虚拟结点

关于是否虚拟结点就要看我们是否要对只有头结点的情况进行操作,本题中完全有可能删除的结点就是头结点,所以为了代码的统一性,我们需要设置虚拟头结点

  • 对于内存的处理

释放虚拟头结点

ListNode* ans = dummy->next;
delete dummy;
return ans

释放被删除结点内存

// 保存要删除的节点,以便后续释放内存
ListNode* toDelete = slow->next;
// 删除节点
slow->next = slow->next->next;

// 释放被删除节点的内存
delete toDelete;

CPP代码

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        // 让fast指针先移动n步
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        // 让fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        fast = fast->next;
        // 移动fast和slow,直到fast指向链表末尾
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        // 保存要删除的节点,以便后续释放内存
        ListNode* toDelete = slow->next;
        // 删除节点
        slow->next = slow->next->next;

        // 释放被删除节点的内存
        delete toDelete;

        // 保存返回结果,并释放虚拟头结点的内存
        ListNode* result = dummyHead->next;
        delete dummyHead;
        return result;
    }
};

面试题 02.07. 链表相交

力扣题目链接

文章链接:面试题 02.07. 链表相交

状态:完成,其实本题和上一题一样,唯一的不同就在于次题确实要遍历出整个链表长度。

基本思路

  • 链表相交处的特征

链表相交处并不是值相等,而是地址相等。

  • 双指针的设计

本题最大的难度就是链表长度不一样,无法找到相交处的主要原因就是,遍历速度不一样。

综上,我们把他们遍历速度变成一致的不就好了?所以假设链表A的长度lengthA,链表B的长度lengthB。如果A比B长的话,sub = lengthA - lengthB。我们让A想跑sub步,这样不就使得A和B的长度一致了吗?

CPP代码

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

力扣题目链接

文章链接:142.环形链表II

视频链接:把环形链表讲清楚!| LeetCode:142.环形链表II

状态:想到了用快慢指针的办法,但是思路还是有问题,一定要分开讨论什么时候有环,在找到环了基础上如何推理出环的入口,同时需要用一点纯数学的思路

主要思路

  • 找到环

设计两个指针相遇,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

  • 找到环的入口

在文章链接中展现了完整的推导过程。142.环形链表II

其中有一段数学推导,最主要的就是证明:
如果有环,那么fast和slow肯定在环内相遇,我们再设计两个指针,一个index1从相遇点开始遍历,一个index2从头结点开始,他们速度一样,并且根据推导,二者肯定会在环口相遇,这样就通过index2找到了环的入口处。

CPP代码

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;
    }
};

你可能感兴趣的:(算法,c++)