代码随想录算法训练营第四天| 24.两两交换、19.删除链表的倒数第N个结点、面试题02.07.链表相交
24.两两交换
题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/
一开始土木就理解错了,我以为的两两交换,是1与2换,1再与3换…(感觉有点像冒泡。通过看卡哥的视频讲解,其实应该是1和2换,3和4换,5和6换,这样两个为一组进行交换。具体代码如下:
ListNode* swapPairs(ListNode* head) {
ListNode*dummyhead =new ListNode();
dummyhead->next = head;
ListNode*cur=dummyhead;
while(cur->next!=NULL&&cur->next->next!=NULL)
{
ListNode*temp = cur->next;
ListNode*temp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = temp;
temp->next = temp1;
cur=cur->next->next;
}
ListNode*result=dummyhead->next;
delete dummyhead;
return result;
}
这题需要注意的点有很多:
首先,要使用虚拟头结点(必会)的方法,可以大大简化逻辑过程。
其次,和反转链表的解题思想类似,我们需要设置temp指针来指向被切断后无法定位的结点。由于在一次循环中,存在两次的切断定位操作,所以需要使用两个临时指针,即temp、temp1指针。
最后,对于循环条件的判断:要分奇偶来讨论,经过画图发现,当偶数个时,cur->nextNULL时循环停止;当奇数个时,cur->next->nextNULL时循环停止。所以循环条件为cur->next!=NULL&&cur->next->next!=NULL。
注意!!cur->next!=NULL&&cur->next->next!=NULL的顺序不可变!如果改变顺序,会造成空指针异常。因为cur->next可能是空指针,这个时候cur->next->next就是对空指针取next,必然是错的。
最后的最后,注意cur=cur->next->next,而不是cur->next->next=cur,联想p=p->next
19.删除链表的倒数第N个结点
题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/
1.直接法(出错)
一眼看到这道题的时候,其实我是有思路的:先遍历一遍得到链表的长度length,然后用length-n得到倒数第N个结点的前一个结点…但是转念一想,这个解法又没啥技术含量,肯定不是最优解。在代码中需要遍历两次,边界条件比较容易出错。代码如下(有问题,不能运行,但边界条件正确):
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode*p=head;
int count=1;
while(p->next!=NULL)
{
p=p->next;//p为尾结点
count++;
}
int count1=1;
ListNode*q=head;
for(;count1<count-n;q=q->next)
{
count1++;
}
ListNode*k=q->next;
q->next=q->next->next;
delete k;
return head;
(上面的代码没有用到虚拟头结点,却没有分类讨论,肯定是错的…)
2.双指针法
准备工作:
while(n–)的语法特点
- 对于while(n),当n为0时,while循环跳出;而对于while(n–),当n为0时,while循环同样跳出,但是有两点不同:一是while(n–)是先看原始的n是否为0,而不是n–之后的结果。二是当n为0时,while(n–)循环跳出,但是n–仍然继续执行。(即原本n为0,经过while(n–)之后,n为-1)
- while(n–)在逻辑上表现为:将循环体执行n次
正式解题:
代码如下:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode*dummyhead =new ListNode;
dummyhead->next=head;
ListNode*fast=dummyhead;
ListNode*slow=dummyhead;
n++;
while(n--&&fast!=nullptr)
fast=fast->next;
while(fast!=nullptr)
{
fast=fast->next;
slow=slow->next;
}
ListNode*k=slow->next;
slow->next=slow->next->next;
delete k;
return dummyhead->next;
}
养成一个好习惯:但凡涉及到链表的操作问题,我们首先都要想到虚拟头结点
这道题的解题技巧非常之妙:设置快慢两个指针。之前讲过的快慢指针是指在范围上或者在速度上,两个指针不一样;这里的快慢指针是指,快指针先走几步,然后快慢指针再一起走。相同点都是快指针要走的更多,慢指针要走得更少
总体思路:先让fast指针跑n+1步,然后fast和slow指针一起跑,等fast指针为null之后,slow指针还有n+1步没跑,也就是说,slow指针此时指向的是倒数第n+1个结点,可由此删除倒数第n个结点。
面试题02.07.链表相交
题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
1.暴力解法
用两个while循环解决问题,代码如下:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*dummy1=new ListNode();
ListNode*dummy2=new ListNode();
dummy1->next=headA;
dummy2->next=headB;
ListNode*A=dummy1;
bool flag=false;
while(A!=NULL)
{
ListNode*B=dummy2;
while(B!=NULL)
{
if(A->next==B->next)
{
flag=true;
goto mark;
}
else
B=B->next;
}
A=A->next;
}
mark:
if(flag)
return A->next;
else
return NULL;
}
暴力解法难点:对于A和B,分别需要设置一个虚拟头结点
补充的知识:
- 判断两个指针p1和p2是否相等,直接上 if(p1==p2)即可(上文的代码里就是if(A->next==B->next)来判断指针所指的地址是否相等)
- 在C++中,想跳出多个循环不能用break,而是用goto,具体方法见上述代码。
2.尾对齐法
我自己写的代码有太多地方需要改进的了,我直接放卡哥的代码吧(跟我不一样的地方我会注释出来):
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;
}
总体思路:让来链表A和B尾对齐,然后调整长链指针的位置,使得两个指针从相同位置向后遍历。
其实思路不难,难点其实就是一些具体操作的细节:
1.
curA = headA;
curB = headB;
将原本尾部的指针重新回到原点(我原本的方法是再设一对指针,其实完全没必要)
2.
while (gap–)
curA = curA->next;
用了一个while(n–),非常巧妙,这样gap的值直接决定了curA的移动次数(我原本的方法是用for循环,费力不讨好)
3. swap()函数的使用,确实省心不少,让代码变得非常简洁