【LeetCode】24. Swap Nodes in Pairs 两两交换链表中的元素

一、概述

题目很直观,两两交换就是两两交换,我觉得不用再解释了。就是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;
    }

首先设置递归出口,当参数为空或者参数的下一个为空时返回,也就是做到最后剩下一个或者不剩下时候返回。

然后让第二个变成第一个,保存指向第三个的指针。让新的第一个指向旧的第一个,旧的第一个指向递归的返回值。

三、总结

题目本身不难,但用二阶指针的方法弄晕我了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值