2023年3月4日
今天的任务有四个
#两两交换链表中的节点 24. 两两交换链表中的节点 - 力扣(LeetCode)
题目要求:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
简单来说,节点之间的交换就是指针交换而并非值交换,所以不能单纯的用双指针同步移动进行值交换
具体的步骤应为下面这张图
![](https://i-blog.csdnimg.cn/blog_migrate/8583e946400a8d3d0d147ab001091156.png)
即先用cur->next = cur->next->next 进行步骤一,然后用cur->next->next = cur->next 进行步骤二,再用cur->next = cur->next->next->next进行步骤三,在这个过程中设置两个临时指针会比较好,以防在交换过程中出现野指针的情况,源码如下(带注释)
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;
}
};
//以下是我自己按照思路写的一遍
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 * temp1 = cur->next;
ListNode * temp2 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = temp1;
cur->next->next->next = temp2;
cur = cur->next->next;
}
return dummyHead->next;
}
};
注意,关于NULL和nullptr的区别可以简单理解为NULL是0,nullptr是空指针,具体讲解请移步(5条消息) C++中NULL和nullptr的区别_nullptr 和null_csu_zhengzy~的博客-CSDN博客
#删除链表的倒数第N个节点 19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
一开始写的时候就简简单单认为直接指针移动到倒数第n+1个节点进行删除不就行了,让最后一个节点指向第一个节点,变成一个单向循环链表,然后再计算链表长度,数学计算啥的,最后找到倒数第n+1个节点进行删除,虽然麻烦但是解决问题
看到代码随想录中提供的思路我觉得真的是抄了“捷径”,通过设置快慢指针,直接让一个慢指针指向了待删除结点的前一个节点(指向前一个节点便于删除),直接省略了我前面的所有步骤,这种方法让我对快慢指针有了新的理解
快慢指针说本质还是双指针,一开始的双指针是相邻的,便于进行数值的交换,后来变成一左一右,向中间移动进行查找,即二分查找,再后来是快慢指针,一是进行数组的覆盖,当两个指针指向同一个数的时候,快指针向右移动一位而慢指针不移动,然后用fast的值对slow的值进行覆盖,然后再同时向右移动快慢指针,直到slow指针指到下一个要删除的元素,再用fast对slow进行覆盖,对于这道题,跟数组的原理是一样的,让一个指针指向待删除的元素,只不过让fast先移动n位我是没想过,由此可见对于双指针的用法还是不太灵活,需要继续加强理解
言归正传,这道题经过刚刚的思路分析,代码如下
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;
}
};
这个代码在不考虑时间复杂度的情况下可以考虑改进的就是那个n--的部分,可以从头到尾遍历一遍求出链表的长度,然后直接用len-n就是fast移动的步数,如果不想让fast移动到null节点的话,让fast移动到最后一个节点也是可以的,这样的话fast和slow同时走,当fast指向最后一个节点的时候,slow也就指向了被删除节点的前一个节点,两种方法应该都是可以的
#链表相交 面试题 02.07. 链表相交 - 力扣(LeetCode)
注意,这道题说的相交指的是地址相交,即节点中的地址是一样的,并不能简单理解为值相等,所以这道题就是需要找到地址一样的两个节点
我一开始想的是,直接设置两个指针分别指向A和B,然后向右移动找到地址一样的两个节点不就行了,代码如下
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode * cura = headA;
ListNode * curb = headB;
while(cura != curb){
cura = cura->next;
curb = curb->next;
}
return cura;
}
};
但是是没法ac的,会出现报错:runtime error: member access within null pointer of type 'ListNode' (solution.cpp),这句话的意思是引用了空指针,也就是说在while循环里面进行判断的时候,即执行cura = cura->next这句话或者curb = curb->next这句话的时候,变成了空指针,会出现报错。那么如果我加上不是空指针呢?如下所示
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode * cura = headA;
ListNode * curb = headB;
while(cura->next != nullptr && curb->next != nullptr && cura != curb){
cura = cura->next;
curb = curb->next;
}
return cura;
}
};
还是没法ac过,这时候的原因就是链表长度不一致导致的问题,因为这种代码默认了两个链表在相同位置产生了相同的节点地址,但实际情况可能并不是这样的,这时候再加上长度的考虑,这时候就有两种方案了,一种是使用双循环,固定一个查找另一个,代码如下
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode * cura = headA;
ListNode * curb = headB;
if(cura == NULL){
return NULL;
}
if(curb == NULL){
return NULL;
}
if(cura == curb){
return curb;
}
for(cura = headA;cura->next!=NULL;cura=cura->next){
for(curb = headB;curb->next!=NULL;curb=curb->next){
if(cura==curb){
return curb;
}
}
}
return NULL;
}
};
这种方式依然有问题,通过41/45,为什么呢?因为你判断的时候总是判断的下一个节点,即cura/b->next 是不是null,相当于你最后一个节点压根没判断,将next去掉即可,代码如下
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode * cura = headA;
ListNode * curb = headB;
if(cura == NULL){
return NULL;
}
if(curb == NULL){
return NULL;
}
if(cura == curb){
return curb;
}
for(cura = headA;cura!=NULL;cura=cura->next){
for(curb = headB;curb!=NULL;curb=curb->next){
if(cura==curb){
return curb;
}
}
}
return NULL;
}
};
我这种方式的时间复杂度是O(n^2),虽然说通过了,但算法的目的就是让时间复杂度尽可能的减少,下面是代码随想录中给的思路
简单来说,就是求两个链表交点节点的指针。 这里同学们要注意,交点不是数值相等,而是指针相等。
为了方便举例,假设节点元素数值相等,则节点指针相等。
看如下两个链表,目前curA指向链表A的头结点,curB指向链表B的头结点:
![](https://i-blog.csdnimg.cn/blog_migrate/c2d3f0d7dd3eed98bce04bfe5e7d0d52.png)
我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/1a6c12d5027da099f87a3e1817e23d25.png)
此时我们就可以比较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;
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;
}
};
这种方法成功将时间复杂度降到O(n+m)了,是一个新的思路,通过改变遍历的位置减少一个for循环
#环形链表II 142. 环形链表 II - 力扣(LeetCode)
我一开始想的是,直接设置一前一后两个指针,当后面指针指向前一个指针的时候,后面指针所指位置就是环形链表入口,后来仔细想一下,发现这种并不普适,有时候是后面的指针指向前一个指针的时候,后面指针是环形链表入口,有时候是两个指针相交的时候是环形链表入口
经过学习我发现,这种题基本都是一个套路:设置快慢指针,快指针一次走两位,慢指针一次走一位,当两个指针“相撞”的时候,就说明有环,之后再对两个指针所指向的位置进行记录,一个指针在当前位置,另一个指针指向最开始的位置,两个指针同时移动,当两个指针“相撞”的时候,就说明这个位置是环形链表入口,代码随想录中写的已经不能更清楚了,我在这里只附上源码,具体思路请移步代码随想录 (programmercarl.com)
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
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;
}
};
那么今天就到这里吧,希望能继续坚持下去,明天见!