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

链表

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

链表这部分的题,强烈建议画图分析。

好的,我们先来画个图,模拟一下未变换之前的链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9f2A7TCE-1668955306779)(2022-11-19-23-52-12.png)]
根据题目的意思,步骤一步骤二和步骤三可以完成一部分结点的互换,那么由于结点的个数不是定值,就需要用到while循环。

我们不妨先设置一个虚拟结点,虚拟结点的作用就是让头结点的前边也存在一个结点,这样修改第一个指针的指向的时候会比较方便。 (修改一个结点的指向的时候,最好找到这个结点的前一个结点)

那问题来了,循环里面放什么条件呢?刚才提到了,结点的个数在给定的范围内是不知道的,那么就要分奇数偶数的情况了,如果是奇数,那么最后一个结点是要被剩下来的,那么也就是说前面的两个结点完成了交换。

虽然我们知道前面的完成了交换,但我们无法判断cur目前指向的位置(cur就是用来交换的,可以参考上面的图),不着急,我们可以先试一试,就以上面的图为例,先只完成一次交换。

我们一开始设置了一个虚拟结点dummyhead,并且让cur指向虚拟结点,我们再看步骤一
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BDjAkRlx-1668955306780)(2022-11-20-00-10-57.png)]
实现的代码如下:

cur -> next = cur -> next -> next;
聪明的小伙伴可能会发现,这个时候cur指向的结点已经不再是1结点了,我们就无法让2再指向1了,所以在进行上面那一步之前,我们要设置一个临时变量来保存1这个结点,就是
ListNode *temp = cur -> next;
那么正常进行操作就可以:
cur -> next -> next = temp;
这样就让2结点指向了1结点。
但3结点由于2结点指向的改变,也无法被得到了,所以我们同样需要一个临时变量来保存3结点,就是
ListNode *temp2 = cur -> next -> next -> next;
cur -> next -> next -> next= temp2;
现在转换的工作完成了,现在要做的就是让循环链接起来,也就是说要把3结点看成刚才的1结点,再次进行循环,重点来了,我们再一开始的时候是让cur等于初始结点的前一个结点,也就是虚拟结点,那么我们如今将3结点看成1进行操作,同样需要将cur放置到3结点的前面,也就是2结点了
cur = cur -> next -> next;
说到这里,我们又想起了刚才的问题,循环条件是什么呢?以这种情况为例,目前cur的位置已经找到了,是在3的前面,那么3和4结点交换完之后,cur会在4这个地方,所以,结点数是偶数的情况下循环条件就找到了,是
cur -> next != NULL
那类比一下,我们在这个后面加上一个结点5,5是不用被换的,cur的位置还是在4结点那里。所以奇数个结点的条件下,循环条件就是
cur -> next -> next != NULL
分析到这里,这道题就没难度了。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode * dummy =new ListNode(0);
        dummy -> next = head;
        ListNode * cur = dummy;
        while(cur -> next != NULL && cur -> next -> next != NULL){
            ListNode *temp = cur -> next;
            ListNode *temp2 = cur -> next -> next -> next;
            cur -> next = cur -> next -> next;
            cur -> next -> next = temp;
            cur -> next -> next -> next= temp2;
            cur = cur -> next -> next;
        }
        return dummy -> next;
    }
};

其实细心的小伙伴会发现,这时候返回的不是head,而是dummy -> next,这是因为在交换结点之后,原本处在链表最左端的head结点会变成第二个结点,输出的时候会忽略掉交换之前的第二个结点,这样如果return 的是head,那么输出的值是没有交换之前的第二个结点的。
由于dummy指向的是第一个结点,所以return dummy就会输出全部链表val域中的值。
我自己的理解,有问题请指出

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

这道题的思路会比较清晰,就是找到要删除结点的上一个结点和下一个结点,然后进行删除操作就好了,但是我们遇到的第一个问题就是怎么找到要删除的倒数第n个结点。
我们可以随手做一下模拟,假设这段链表结点的总个数为x,要删除第n个结点,那么在结点n之前一共有x-n个结点,也就是说从结点1开始,我们要让指针移动x-n个结点。

在不获取链表长度的前提下,我们可以将快慢指针的知识联系起来,我们可以把刚才提到的指针看成慢指针,然后再设置一个快指针,快指针起的作用就类似于获取链表长度。

我们先让快指针走n个结点,然后让快慢指针一起走,当快指针走到最后一个结点的时候,慢指针的位置就是第n个结点的前一个。相当于一开始快指针走n个单位,这个时候快慢指针一起走,就可以让慢指针走x-n个单位。
(所以快指针的作用有两个,一个是帮助慢指针定位,一个是便于删除结点)
根据我做题的经验,完成上述代码的实现之后,往往会在一些比较特殊的情况卡住,比如链表长度等于2,n也等于2,实践发现,上述代码还需要改进。

因为删除结点的时候最好知道这个结点的上一个和下一个结点,对于第一个结点来说,是没有上一个结点的,所以我们可以设置一个虚拟结点dummy head,让快慢指针一开始都指向虚拟结点,这样首元素删除的问题就解决了。

没完,如果长度为2,n为1呢。
这个时候我们按照刚才的思路实现之后会发现,快指针指向的是最后一个结点,慢指针指向虚拟结点的下一个结点,这样无法完成对最后一个元素的删除,所以我们需要让快指针指向最后一个结点的下一个结点,也就是NULL,这样就可以完成对最后一个结点的删除,(因为可以让倒数第二个结点直接指向NULL).
小二,上代码!

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 nth;
        
        return dummyHead->next;
    }
};

根据经验,需要仔细考虑链表长度为1和为2的情况

142.环形链表II

链表题目一般用双指针啦~

这道题其实有两问,第一问是判断是否有环,第二问是找到环对应的结点。
对于第一问来说,我们假设慢指针每次走一个结点,快指针每次走两个结点,如果链表没有环,那么快慢指针永远无法相遇,如果存在环,那么快慢指针的相对速度为1,那么这两个指针也一定相遇。
假设从头结点到环形入口节点 的节点数为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 ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z,

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

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

只能说,carl讲的非常清楚,思路非常清晰

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值