上回说到面试中的一个高频题目:单链表反转,提到它的“难兄难弟”不是那么简单。今天就来分析一下它的二哥:“两两交换链表中的节点”。
题目描述如下。
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
方法一:三指针迭代
有了上回单链表反转的双指针迭代法的基础,再来解决它二哥,就相对容易很多了。
“巧妇难为无米之炊”。题目中说的是要两两交换,那首先得有两个节点才行,所以上来先判断够两个节点才继续交换,而且循环继续的条件也是要满足剩下的节点够两个才行。
我们可以使用两个变量first
和second
来表示当前要进行交换的第一个节点和第二个节点。
如上图,交换后,2nd
节点指向1st
节点,而1st
节点不能再指向2nd
节点了,否则就形成了环。应该让1st
节点指向2nd
节点原来的下一个节点,所以我们需要事先把2nd
节点原来的下一节点记录下来。
交换操作伪代码如下:
ListNode third = second.next;
second.next = first;
first.next = third;
同时,要考虑下面的这种场景,节点1和节点2已经完成交换,现在轮到节点3和节点4的交换,要保证节点3和节点4交换后,节点1指向的是节点4,所以还要记录每一轮交换的前驱节点,下图这个例子里面前驱节点就是节点1。
最后要返回的头指针为第一轮交换中的第二个节点。
动画演示如下。
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null, first = head, second = head.next;
ListNode ans = second;
while (first != null && second != null) {
ListNode third = second.next;
second.next = first;
first.next = third;
// 记录前驱节点
if (pre != null) {
pre.next = second;
}
pre = first;
// 1st,2nd 向前走
first = third;
second = (first != null) ? first.next : null;
}
return ans;
}
}
复杂度分析
- 时间复杂度:
O(n)
。 - 空间复杂度:
O(1)
。
方法二:递归
上回说到的递归的时候,我写了一段自己都递归的感悟,这里忍不住再写一遍。
递归是个神奇的存在,那么简单,又那么复杂,有时觉得它很近,实际上它却那么远,有时觉得重新认识了它,可它还是那个它,从未改变过。每一次使用递归,都会对它理解更深一点。
如果把交换两个节点看做一个回合的话,那么两两交换链表中的所有节点是由很多这样的回合组成。递归是要倒着来的,就是说我们在进行当前回合的交换的时候,可以假定将来的回合已经完成了。
如下示意图所示,还拿链表1->2->3->4->5->null
来举例,在进行节点1和节点2的交换的时候,假定3->4->5->null
已经完成了交换,节点1指向swapPairs(3)
返回的节点:
代码如下:
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode first = head, second = head.next;
first.next = swapPairs(second.next);
second.next = first;
return second;
}
}
复杂度分析
- 时间复杂度:
O(n)
。 - 空间复杂度:
O(n)
,使用了递归,递归的栈深度为n
。
下集预告,单链表反转的“大哥”:
- K 个一组翻转链表
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。