赞
踩
题解参考:代码随想录 (programmercarl.com)
原题链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
[0, 100]
内0 <= Node.val <= 100
这道题目,建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要注意操作的先后顺序
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
看这个可能就更直观一些了:
这个版本就是通过while循环实现上述交换过程。注意这里的循环终止条件
cur->next
为空,退出循环cur->next->next
为空,退出循环while(cur->next && cur->next->next)
,注意:cur->next
一定要写在前面,不然可能会出现空指针异常,因为如果为偶数的时候,cur->next
已经为空,再去取cur->next
的next肯定会报错代码如下所示
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ // 迭代版本 struct ListNode* swapPairs(struct ListNode* head){ // 起别名 typedef struct ListNode ListNode; // 开辟虚拟头节点 ListNode* dummyHead = (ListNode*)malloc(sizeof(ListNode)); dummyHead->next = head; // 当前节点 ListNode* cur = dummyHead; // 当链表节点数为偶数时:cur->next 为空,退出循环 // 当链表节点数为奇数时,cur->next->next 为空,退出循环 while(cur->next && cur->next->next) { // 临时节点1,第一次保存1 ListNode* tmp1 = cur->next; // 临时节点2,第一次保存3 ListNode* tmp2 = cur->next->next->next; // 交换节点 cur->next = cur->next->next; cur->next->next = tmp1; cur->next->next->next = tmp2; // cur后移两位 cur = cur->next->next; } return dummyHead->next; }
O(n)
,其中*n
*是链表的节点数量。需要对每个节点进行更新指针的操作。O(1)
。递归版本的实现逻辑和迭代是一样的,只不过使用递归的方式代替了循环。
递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。
用head
表示原始链表的头节点,新的链表的第二个节点,用newHead
表示新的链表的头节点,原始链表的第二个节点,则原始链表中的其余节点的头节点是 newHead.next
。令head.next = swapPairs(newHead.next)
,表示将其余节点进行两两交换,交换后的新的头节点为head
的下一个节点。然后令 newHead.next = head
,即完成了所有节点的交换。最后返回新的链表的头节点newHead
。
处理前:
处理后:
然后依次递归处理其余节点,直到遇到终止条件。
使用递归,我们需要注意的有三点:
在本题中:
head
和newHead
,head
连接后面交换完成的子链表,newHead
连接head
,完成交换head
为空指针或者newHead
为空指针,也就是当前无节点或者只有一个节点,无法进行交换C语言递归版本一:先递归,后交换,这个版本和上述的分析流程一致
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ // 递归版本1 struct ListNode* swapPairs(struct ListNode* head){ // 起别名 typedef struct ListNode ListNode; // 递归终止条件,链表中没有节点,或者链表中只有一个节点,此时无法进行交换。 if(head == NULL || head->next == NULL) { return head; } // 原始链表的第二个节点,新链表的头节点 ListNode* newHead = head->next; // newHead->next指向的是后面未处理的链表 // 第一次处理完后变成2->1->3->4,newHead->next指向的是3 head->next = swapPairs(newHead->next); // 将新的头节点的next指针指向老的头节点 newHead->next = head; return newHead; }
O(n)
,其中*n
*是链表的节点数量。需要对每个节点进行更新指针的操作。O(n)
,其中*n
*是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。C语言递归版本二:先交换,后递归。这个版本和上面的略有不同,但是更便于理解
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ // 递归版本2 struct ListNode* swapPairs(struct ListNode* head){ // 起别名 typedef struct ListNode ListNode; // 递归终止条件,链表中没有节点,或者链表中只有一个节点,此时无法进行交换。 if(head == NULL || head->next == NULL) { return head; } // 原始链表的第二个节点,新链表的头节点 ListNode* newHead = head->next; // 交换节点 head->next = newHead->next; newHead->next = head; // 此时head->next指向的是下一次要处理的链表头节点 // 第一次处理完之后为2->1->3->4,此时head->next指向的是3 head->next = swapPairs(head->next); return newHead; }
O(n)
,其中*n
*是链表的节点数量。需要对每个节点进行更新指针的操作。O(n)
,其中*n
*是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。Java递归版本二:先交换,后递归
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ // 递归版本2:Java版,先交换,后递归 class Solution { public ListNode swapPairs(ListNode head) { if(head == null || head.next == null) { return head; } ListNode newHead = head.next; head.next = newHead.next; newHead.next = head; head.next = swapPairs(head.next); return newHead; } }
O(n)
,其中n
是链表的节点数量。需要对每个节点进行更新指针的操作。O(n)
,其中n
是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。这道题目一定要画图,不画图,操作多个指针很容易乱,而且要注意操作的先后顺序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。