一、概述
题目很直观,两两交换就是两两交换,我觉得不用再解释了。就是1234变成2143。
我的思路也很好想:把原来的链表拆成奇链表和偶链表,原来的顺序是奇偶奇偶,之后是偶奇偶奇。
二、分析
1、我的方法
很繁琐,主要是把链表分离开很麻烦。如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head==NULL)
return head;
if(head->next==NULL)
return head;
ListNode *even=head->next;
ListNode *odd =head;
ListNode *now = head->next->next;
ListNode *evenNow, *oddNow;
evenNow = even;
oddNow = odd;
while (now)
{
oddNow->next = now;
evenNow->next = now->next;
oddNow = oddNow->next;
evenNow = evenNow->next;
if (now->next == NULL)
break;
now = now->next->next;
}
if(oddNow!=NULL)
oddNow->next = NULL;
if(evenNow!=NULL)
evenNow->next = NULL;
ListNode *result=new ListNode(-1);
now = result;
int num = 1;
while (even!=NULL&&odd!=NULL)
{
if (num % 2 == 1)
{
num++;
now->next = even;
even = even->next;
now = now->next;
}
else
{
num++;
now->next = odd;
odd = odd->next;
now = now->next;
}
}
if (even==NULL)
now->next = odd;
if (odd==NULL)
now->next = even;
return result->next;
}
};
有几个语法问题要注意:
第一个,声明一个结构体,如果没有new一个对象给它,那这个结构体就不能作为等号右边去赋值给别人。如下:
ListNode *head;
ListNode *now;
now = head;
但是你new之后就可以了:
ListNode *head=new Listnode(0);
ListNode *now;
now = head;
写完这句我才发现我傻逼了。怎么int a,int b,b=a我就知道不行,换成结构体就蒙了呢?
你不new就没分配内存,也就可以说是没赋值,那怎么把它的值给别人啊。
第二个,在把俩链表组合成一个的时候,这样写是不对的:
ListNode *result=new ListNode(-1);
now = result;
while (even || odd)
{
now = even;
now->next = odd;
even = even->next;
odd = odd->next;
now = now->next->next;
}
看上去挺好的,now是第一个,然后把偶链表的第一个赋值给now,奇链表的第一个赋值给now的下一个,然后俩链表向后移动,now向后移动两个。很好。
但是问题出在哪里呢?
来看第二行,把result赋值给now。然后进循环,把even的第一个赋值给now。从此以后的操作就和result没关系了。
并不是
now=result;
now=even;
就能把even给result的。
而且,观察结构体,它也有问题:
首先even的第一个赋值给now,odd的第一个赋值给now的下一个。好,那now的下一个就是even的下一个,也就是even的下一个现在是odd的第一个。把odd插进even里面了,这太蠢了。会造成死循环。
因此对于链表的合并啊什么的,不要now->next刚赋值完就去赋值now->next->next,要是真有这需求,这样做:
ListNode *result=new ListNode(-1);
now = result;
while (even || odd)
{
now = even;
even = even->next;
now->next = odd;
odd = odd->next;
now = now->next->next;
}
一旦even的第一个用完了,even就跑到下一个。这样now->next赋值为odd的时候,even本身不受影响。
而且这样改完还是不对。为什么?我们来看下面的代码:
ListNode *head;
ListNode *l1 = new ListNode(1);
ListNode *l2 = new ListNode(2);
head = l1;
l1 = l2;
cout << head->val;
输出为1,而不是2。
为什么?因为head、l1、l2都是指针,指向的是一块内存。它们本身并不是结构体。因此当执行head=l1的时候,实际上就是让head指向l1指向的内存,这个内存里存着val=1。然后执行l1=l2的时候,实际上是让l1这个指针指向l2的内存,l1原来指向的内存中的数据没有改变,还是1,因此第二步不会改变head的数据。
我的误解在于,l1=l2这一步,我认为是l1指向的内存中的数据变为l2指向的内存中的数据,然后head指向的数据就变化了。实际上没有变化。这就和下面的代码一样:
int head;
int a=1;
int b=2;
head=a;
a=b;
cout<<head;
这样就很容易看出来head是1。我怕不是喝了假酒。
ListNode *head;
ListNode *l1 = new ListNode(1);
ListNode *l2 = new ListNode(2);
head = l1;
*l1 = *l2;
cout << head->val;
这样的输出才是2。
也就是说,要想往链表后加东西,除了最开始可能用赋值的方法之外,之后都要用next来指向,循环体中就不要有赋值操作了,太愚蠢。
2、使用指针的指针
先看代码吧。
ListNode* swapPairs(ListNode* head) {
ListNode **pp = &head, *a, *b;
while ((a = *pp) && (b = a->next)) {
a->next = b->next;
b->next = a;
*pp = b;
pp = &(a->next);
}
return head;
}
我们得一起捋一捋这个指针的指针是怎么运作的。
第一行不用看。直接看第二行,pp是指针的指针,它的值是什么呢?是输入的链表head的地址。这样,*pp就是head的值,**pp就是链表的第一个节点。a和b都是节点指针。如上图所示。
接下来开始循环,循环的退出条件是pp为NULL或pp的下一个为NULL。这个先放在一边。接下来看循环体。
首先,在循环开始的时候已经令a指向链表的第一个元素,b指向链表的第二个元素。然后将b的下一个元素接到a后面,b自己的下一个指向a,实现了ab位置的调换。如下图:
然后问题来了,我们的链表第一个元素现在还是1,要想让它变成2,怎么变呢?这就要修改head的值。因此有了循环体的第三行。修改完head的值以后,pp要走向3,进行下一对的替换。如图:
继续循环即可。
我们不明白的地方主要就是第三步和第四步,不知道它有什么用。来看下面这张图片:
第一行是链表的初始状态,第二行是链表执行循环体前两行之后的状态。我们会发现,调换后第二个元素的next的指向是错误的,但是要明确一点:在本次循环中,这个错误不会被更正。要等到下一次循环才更正。本次循环的后两行是更正上一次循环的错误,让上一次循环的指针指向第二个元素,对于第一次循环就是让head指向正确的元素。
如何让上一次循环中的指针指向正确的元素呢?我们需要保存上一次循环中一对元素中调换后第二个的next,本质上说就是它指错了。如何保存这个指针?不能用一个节点指针去保存,如果这样保存,类似如下代码:
tmp=b->next;
a->next=b->next;
b->next=a;
a=tmp;
b=tmp->next;
这样可以么?不可以。
以1、2、3、4、5为例
第一行,tmp指向b的下一个元素3。
第二行,a的下一个元素指向b的下一个元素,即1->3。
第三行,b的下一个元素指向a,即2->1。
第四行,将tmp赋值为a,a指向3。
第五行,b指向4。
发现了么,1->3是不对的,应该是1->4,但是循环体中没有保存1的next。这就是问题所在。
可以使用二阶指针保存1的next。
可以通过对照图三来看。在调换后,上面的0x0003变成了0x0001,但是下面的0x0003没有变。即上面的指针发生了变化,但下面的没变。所以保存下面的是可行的。
循环体的最后一句pp=&(a->next)就是做这个的。
而循环体的第三句则是用于修改上一次循环的错误的。
3、递归
这个思路就很简单了。直接看代码吧:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
// 2 is new head, 1 is head
ListNode* new_head = head->next;
// store 3
ListNode* third = head->next->next;
// 2->1
new_head->next = head;
// 1->3
head->next = swapPairs(third);
return new_head;
}
首先设置递归出口,当参数为空或者参数的下一个为空时返回,也就是做到最后剩下一个或者不剩下时候返回。
然后让第二个变成第一个,保存指向第三个的指针。让新的第一个指向旧的第一个,旧的第一个指向递归的返回值。
三、总结
题目本身不难,但用二阶指针的方法弄晕我了。