重排链表
约 1054 字大约 4 分钟
2025-02-28
143. 重排链表
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[1,4,2,3]
示例 2:
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]
提示:
- 链表的长度范围为
[1, 5 * 104]
1 <= node.val <= 1000
双指针
找到链表的中间节点,将链表分成两部分,反转链表的后半部分,合并两个链表。
因为两链表长度相差不超过 1,因此直接合并即可。
class Solution {
/**
* 重新排序链表,将链表 L0 → L1 → ... → Ln-1 → Ln 重新排序为 L0 → Ln → L1 → Ln-1 → L2 → Ln-2 ...
*
* @param head 链表的头节点
*/
public void reorderList(ListNode head) {
// 如果链表为空,直接返回
if (head == null) {
return;
}
// 找到链表的中间节点
ListNode mid = middleNode(head);
ListNode l1 = head; // 链表的前半部分
ListNode l2 = mid.next; // 链表的后半部分
mid.next = null; // 将链表分成两部分
l2 = reverseList(l2); // 反转链表的后半部分
mergeList(l1, l2); // 合并两个链表
}
/**
* 找到链表的中间节点
*
* @param head 链表的头节点
* @return 链表的中间节点
*/
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
// 快慢指针寻找链表的中间节点。链表长度为偶数时,fast会停在倒数第二个节点上,因为它的next.next是空。
// fast停的位置是小于等于链表长度的最大奇数长度。这样才能保证长度为偶数时,slow停在中间偏左的位置。
while (fast.next != null && fast.next.next != null) {
slow = slow.next; // 慢指针每次移动一步
fast = fast.next.next; // 快指针每次移动两步
}
return slow; // 返回慢指针指向的节点,即链表的中间节点。奇数长度就是中间点,偶数长度就是中间偏左的位置。
}
/**
* 反转链表
*
* @param head 链表的头节点
* @return 反转后的链表头节点
*/
public ListNode reverseList(ListNode head) {
ListNode prev = null; // 前一个节点
ListNode curr = head; // 当前节点
// 逐步反转链表
while (curr != null) {
ListNode nextTemp = curr.next; // 保存下一个节点
curr.next = prev; // 当前节点的 next 指向前一个节点
prev = curr; // 前一个节点更新为当前节点
curr = nextTemp; // 当前节点更新为下一个节点
}
return prev; // 返回反转后的新头节点
}
/**
* 迭代交叉合并两个链表,也可以递归
*
* @param l1 第一个链表的头节点
* @param l2 第二个链表的头节点
*/
public void mergeList(ListNode l1, ListNode l2) {
ListNode l1_tmp; // 临时保存 l1 的下一个节点。只是定义,并没有初始化。
ListNode l2_tmp; // 临时保存 l2 的下一个节点
// 交替合并两个链表。结束条件是重点。
// 根据题意,l1的长度一定大于等于l2的长度,且最多多1个节点。正因如此,我们才可以这样写while的结束条件。
// 即l1和l2要么同时为空,要么一个为空,另一个只剩一个节点。
while (l1 != null && l2 != null) {
l1_tmp = l1.next; // 保存 l1 的下一个节点
l2_tmp = l2.next; // 保存 l2 的下一个节点
l1.next = l2; // l1 的 next 指向 l2
l1 = l1_tmp; // l1 移动到下一个节点
l2.next = l1; // l2 的 next 指向 l1
l2 = l2_tmp; // l2 移动到下一个节点
}
}
/**
* 递归合并两个链表,将第二个链表的节点交替插入第一个链表。
*
* @param l1 第一个链表的头节点(主链表)
* @param l2 第二个链表的头节点(插入链表)
*/
public void mergeList(ListNode l1, ListNode l2) {
// 递归结束条件:当l2为空时,不需要再进行合并
if (l1 == null || l2 == null) {
return;
}
// 保存 l1 和 l2 的下一个节点
ListNode l1Next = l1.next;
ListNode l2Next = l2.next;
// 将 l2 插入到 l1 后面。两步操作
l1.next = l2;
l2.next = l1Next;
// 递归处理剩余部分
mergeList(l1Next, l2Next);
}
}
- 时间复杂度:O(N),其中 N 是链表中的节点数。
- 空间复杂度:O(1)。