1、 题目描述
🥱给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 空链表或者一个节点无需交换 图示:
graph LR
A((node1=1)) --> B((node2=2))
B --> C((3))
C --> D((4))
style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
E((node2=2)) --> F((node1=1))
F --> G((4))
G --> H((3))
style E fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
style G fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
2、思路
输入链表、顺序交换链表中的两两节点,返回交换后的链表。 与递归的逻辑相同(将大问题分解成相似的小问题) 总所周知~ 能用递归的算法最终都可以通过迭代实现,所以这道题基本可以得到递归与迭代两种解法。
递归:
- 优点:代码逻辑简单、通过终止条件自动处理边界
- 缺点:空间复杂度较大、有栈溢出风险
迭代:
- 优点:易于调试、空间复杂度底
- 缺点:代码逻辑稍复杂
3、代码
递归
/**
* 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; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
// 递归退出条件
if(head == null || head.next == null){
return head;
}
// 新链表头节点
ListNode newListHead = head.next;
// 老链表头节点 指向交换后链表的头节点
head.next = swapPairs(newListHead.next);
// 新链表头节点 指向老链表头节点
newListHead.next = head;
// 返回新链表
return newListHead;
}
}
迭代
/**
* 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; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
// 构建空头节点(方便交换后返回新链表)
ListNode tempHead = new ListNode(0);
tempHead.next = head;
// 临时指针(后续使用临时指针进行移动)
ListNode tempPointer = tempHead;
// 循环条件、符合条件才进行交换
while(tempPointer.next != null && tempPointer.next.next != null){
// 将 node1、node2 单独声明出来方便进行节点交换
ListNode node1 = tempPointer.next;
ListNode node2 = tempPointer.next.next;
// 1. 头节点指向 node2
tempPointer.next = node2;
// 2. node1 指向 node2 后继节点
node1.next = node2.next;
// 3. node2 指向 node1
node2.next = node1;
// 以上1、2、3步后成功交换两个节点
// node1 后续节点还没有交换,赋值给tempPointer后 循环进行后面节点的交换逻辑
tempPointer = node1;
}
// 返回头节点的后继节点 即为交换后的链表
return tempHead.next;
}
}
迭代图式: 第一步:
graph LR
head(head=0) --> B
A((1)) --> B((2))
B --> C((3))
C --> D((4))
style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
第二步:
graph LR
head(head=0) --> B((2))
A((1)) --> C
B --> C((3))
C --> D((4))
style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
第三步:
graph LR
head(head=0) --> B((2))
A((1)) --> C((3))
B --> A
C --> D((4))
style B fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
style D fill:#000000,color:#FFFFFF,stroke:#333,stroke-width:2px
4、算法分析
复杂度分析
时间复杂度: 两种解法都是遍历一遍链表 时间复杂度相同为 O(n) 空间复杂度: 递归会占用额外的 O(n)级别的栈空间、迭代几乎没有耗费额外空间为O(1) 实际执行耗时与内存占用率:
5、总结
递归与迭代算法各有优劣、不同场景使用不同算法思路解决相同问题, 碰到一时难以解决的问题可以先通过递归写出解法,然后根据递归思路慢慢调试出迭代的写法。