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

第四天,第一个周末到啦,虽然只有4天,但也算坚持一周了ヾ(◍°∇°◍)ノ゙💪,编程语言:C++

目录

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

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

面试题02.07.链表相交

142.环形链表II

总结:


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

文档讲解:代码随想录两两交换链表中的节点

视频讲解:手撕两两交换链表中的节点

题目: 

初看: 第一想法是采取昨天的双指针方法,两个指针分别指向要交换的节点,接着先让第一个节点的next等于第二个节点的next,再让第二个节点的next等于第一个节点。这样就实现了第二个节点指向第一个节点,第一个节点指向第三个节点的交换。但细想发现,这样的话实际上第二个节点没有彻底取代第一个节点,也就是前一个节点没有指向新的“第一个节点”。因此还需要对前节点进行处理。也就是一次得处理三个节点。

学习:通过文档视频讲解发现,本题与之前的双指针方式不同,由于要同时处理三个节点,所以首先要明确当前节点cur是第几个节点。显然由于是单链表,只有next指针,因此当前节点cur为前一节点(要交换的两个节点的前一节点)能够直接处理后两个节点。同时为了避免节点的丢失,需要采用两个中间指针保存两个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;
    }
};

改进代码:在进行代码编写的过程中,我发现如果将步骤三与步骤二交换同样不会导致节点的丢失,同时还能够减少一个中间节点的保存。

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyhead = new ListNode(0);
        dummyhead->next = head;
        ListNode*cur = dummyhead;
        while (cur->next != nullptr && cur->next->next != nullptr) {
            ListNode* temp = cur->next;
            cur->next = cur->next->next;  //第一步骤
            temp->next = cur->next->next; //第三步骤
            cur->next->next = temp;       //第二步骤
            cur = temp;
        }
        ListNode* result = dummyhead->next;
        delete dummyhead;
        dummyhead = nullptr;
        return result;
    }
};

收获:

  1. 链表的处理,最重要的是要确定一次要处理的节点有几个,且当前节点cur是哪个节点,只有确定好这两点,才能够有条不紊的进行下去。
  2. 同时在处理链表时,还需要时刻注意节点的丢失,合理的保存中间节点。

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

文档讲解: 代码随想录删除链表的倒数第N个节点

视频讲解:手撕删除链表的倒数第N个节点

题目:

初看: 第一个想法是先遍历链表,确定链表的个数size,然后再次遍历直到遍历到第size-n个节点,则下一个节点就是要删除的节点,之后进行节点的删除释放即可。(例如总共有10个节点,要删除的节点是倒数第2个节点,则遍历到第(10-2=8)个节点即可(遍历到要删除的节点的前一个节点)。该方法的时间复杂度为O(L),L为链表长度。

学习:通过文档视频学习到一个新的方法,使用双指针能够一次遍历找到要删除的目标节点。具体为设置一对快慢指针,初始均指向虚拟头节点(使得头节点能够同一处理),由于要删除倒数第n个节点,则可以让快指针先往前走n个节点。之后再让快慢指针同时往后走。直到快指针的下一个指针为空时停下,此时快指针指向了最后一个节点,而慢指针指向要删除的节点的前一个节点。(例如总共有5个节点,要删除倒数第2个节点,快指针遍历n个节点,到达了第2个节点,之后快慢指针同时往前走,快指针走到第三步时,到达第5个节点也就是最后一个节点,此时慢指针到达第三个节点,要删除的倒数第二个节点(第四个节点)的前一个节点。)

代码:

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyhead = new ListNode(0);
        dummyhead->next = head;
        ListNode* fast = dummyhead;
        ListNode* slow = dummyhead;
        while (n-- && fast->next != nullptr) { //快指针先走n步
            fast = fast->next;
        }
        while (fast->next != nullptr) { //快慢指针同时前进
            slow = slow->next;
            fast = fast->next;
        }
        ListNode* temp = slow->next; 
        slow->next = slow->next->next; //进行指针删除
        delete temp;
        temp = nullptr;
        return dummyhead->next;
    }
};

收获:

  1. 学习到双指针的另一个用法:确定链表倒数第n个节点。 这种方法,我认为重点在于确定快指针的终点,以及慢指针此时的位置。

面试题02.07.链表相交

 文档讲解:代码随想录面试题 02.07. 链表相交

题目: 

 示例:

初看: 第一想法由于有两个链表,因此肯定需要有两个指针分别指向两个链表的节点。难点在于如何判断两个指针指向的节点是否为交点,以及如何保证两个节点能够在交点相遇。基于这两点有个想法,可以先分别遍历两个链表,确定两个链表的长度。两个链表如果相交的话,则后半段一定是相同的,因此确定好链表长度之后,可以让长的链表指针先走两个链表的差值,此时两个指针就走到了同一起跑线上,之后两个指针同时向后遍历,直到指向的是同一个指针则找到了交点,如果没有则不存在交点。

代码:

//时间复杂度:O(n+m) 两个链表的长度之和
//空间复杂度:O(1)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == NULL || headB == NULL) return NULL;
        int nA = 0, nB = 0;
        ListNode* curA = headA;
        ListNode* curB = headB;
        while (curA != NULL) {  //求A链表长度
            nA++;
            curA = curA->next;
        }
        while (curB != NULL) {  //求B链表长度
            nB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        if (nA >= nB) {         //判断哪个链表为长链表
            int n = nA - nB;
            while (n--) {
                curA = curA->next;
            }
            while (curA != curB && curA != NULL && curB != NULL) {
                curA = curA->next;
                curB = curB->next;
            }
        }
        else {
            int n = nB - nA;
            while (n--) {
                curB = curB->next;
            }
            while (curA != curB && curA != NULL && curB != NULL) {
                curA = curA->next;
                curB = curB->next;
            }
        }
        return curA;  //找到交点则curA指向交点,未找到交点则curA此时也指向了NULL
    }
};

收获:

  1. 本题主要就是在于逻辑分析和模拟过程,我认为重点在于去尝试进行模拟,很多题可能会有很简便的方式,但模拟法同样是重要的解决办法之一。

142.环形链表II

文档讲解:代码随想录环形链表II

视频讲解:手撕环形链表

题目:

示例:

 

初看: 第一想法是与前一道链表相交是否相同,能否通过双指针的方式找到相交节点。发现并不能,首先如果链表内有环的话,一次遍历无法确定链表内元素个数,也无法确定交点。无法解出。

学习:学习文档视频后发现本题可采用快慢指针的方式找到环的所在,即快指针每次走两步,慢指针每次走一步。如果有环的话,那快指针必定先进入环,且在环内循环移动,慢指针后进入环。又因为快指针相对于慢指针实际上只多移动一格,类似于跑步,由于只多移动一格,则快指针对慢指针进行超圈的时候,快慢指针一定会相遇,此时循环结束。同时如果没有环,则快指针会先到达NULL,循环结束,返回NULL。

找到环所在后,可以将整个链表分为三段:

而由于fast每次走两步,slow每次走一步,因此fast走过的节点数是slow走过的节点数的两倍。即x+y+n(y+z) = 2(x+y),则可以得到x=n(y+z)-y。由于n至少为1(fast至少转了一圈与slow相遇),则可以取一个y+z,上式变为x=z+(n-1)(y+z)。由于y+z就为环的长度,指针在内部每转一圈则走y+z步,则可以发现如果让一个节点index在fast与slow的相遇位置,一个节点在头节点,两个节点同时向后遍历,则节点ptr在旋转(n-1)圈后,再走z个节点到达环形入口点,此时头节点也到达环形入口点,也就是这两个节点一定会在环形入口点相遇。

代码:

//时间复杂度O(n)
//空间复杂度O(1)
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* ptr = head; //头节点开始移动
                while (ptr != slow) { //solw节点作为index节点
                    ptr = ptr->next;
                    slow = slow->next;
                }
                return ptr;
            }
        }
        return NULL;
    }
};

收获:

  1. 通过本题了解到了一种找到链表内是否有环,环的交点在哪的方法。采用快慢指针相对移动一格来找到环内所在,并且只能是一格,如果超过1格则可能会出现套圈,无法相遇的情况。
  2. 分析链表环的时候,通过例图分析更加直观,也考验了数学逻辑能力。 

总结:

第四天刷题,环形链表II未能解出,数学逻辑能力还不够,继续刷题💪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值