Problem: 148. 排序链表
题目:给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
整体思路
这段代码旨在解决一个经典的链表问题:排序链表 (Sort List)。问题要求对一个给定的单向链表进行升序排序。
该实现采用了一种非常直观但不符合题目通常要求(题目通常要求 O(N log N) 时间复杂度和 O(1) 空间复杂度,即原地归并排序)的方法。它将链表问题转化为了数组问题来解决,其核心逻辑步骤如下:
-
抽取链表数据到数组(列表):
- 算法首先创建了一个动态数组
ArrayList<Integer> temp
。 - 然后,通过一次遍历,将链表中所有节点的值(
cur.val
)按顺序抽取出来,并添加到temp
列表中。 - 目的:这样做将链表的排序问题“降维”成了一个我们更熟悉的数组排序问题。数组(或列表)提供了比链表更灵活的随机访问和排序能力。
- 算法首先创建了一个动态数组
-
对数组进行排序:
- 在所有节点值都存入
temp
列表后,算法调用Collections.sort(temp)
。 Collections.sort()
是 Java 集合框架提供的强大排序工具,它底层对于ArrayList
使用的是一种经过优化的归并排序(Timsort),能够高效地对列表中的元素进行升序排序。
- 在所有节点值都存入
-
将排序后的数据写回链表:
- 现在
temp
列表中存储的是排序好的节点值序列。 - 算法再次从头遍历原始链表(
cur = head
)。 - 在遍历过程中,将
temp
列表中排好序的值,按顺序逐一写回到链表的对应节点中。即cur.val = temp.get(i++)
。 - 这一步只修改了节点的值 (
val
),而没有改变链表的结构(即节点的next
指针关系)。
- 现在
-
返回结果:
- 当所有节点的值都被更新为排序后的值之后,原始链表的结构并未改变,但其内容已经有序。直接返回原始的头节点
head
即可。
- 当所有节点的值都被更新为排序后的值之后,原始链表的结构并未改变,但其内容已经有序。直接返回原始的头节点
总结来说,这是一个通过“借用”数组的排序能力来间接完成链表排序的解法。它思路简单,易于实现,但在面试或竞赛中通常不被认为是标准答案,因为它没有在链表结构上直接进行排序操作。
完整代码
/**
* Definition for singly-linked list.
*/
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 {
/**
* 对一个单向链表进行升序排序。
* @param head 链表的头节点
* @return 排序后链表的头节点
*/
public ListNode sortList(ListNode head) {
// 步骤 1: 将链表节点的值抽取到一个动态数组中
List<Integer> temp = new ArrayList<>();
ListNode cur = head;
while (null != cur) {
temp.add(cur.val);
cur = cur.next;
}
// 步骤 2: 使用集合工具类对数组进行排序
// Collections.sort() 底层使用高效的排序算法(如Timsort)
Collections.sort(temp);
// 步骤 3: 将排序好的值写回到原始链表的节点中
cur = head; // 重置指针到链表头部
int i = 0; // 数组索引
while (null != cur) {
// 更新当前节点的值为排序后数组中的对应值
cur.val = temp.get(i++);
cur = cur.next;
}
// 返回原始链表的头节点,此时其节点值已经有序
return head;
}
}
时空复杂度
时间复杂度:O(N log N)
- 抽取数据:第一个
while
循环遍历整个链表一次,将N
个元素添加到ArrayList
中。这个过程的时间复杂度为 O(N)。 - 排序:
Collections.sort(temp)
是算法中时间开销最大的部分。对一个包含N
个元素的列表进行排序,其时间复杂度为 O(N log N)。 - 写回数据:第三个
while
循环再次遍历整个链表一次,更新N
个节点的值。这个过程的时间复杂度为 O(N)。
综合分析:
算法的总时间复杂度由排序主导:O(N) + O(N log N) + O(N)。因此,最终的时间复杂度是 O(N log N)。
空间复杂度:O(N)
- 主要存储开销:算法创建了一个
ArrayList<Integer> temp
来存储链表中所有节点的值。 - 空间大小:如果链表的长度为
N
,那么temp
列表的大小也将是N
。 - 其他变量:
cur
,i
等变量只占用常数级别的空间,即 O(1)。 - 排序的额外空间:
Collections.sort()
使用的 Timsort 算法需要额外的空间,最坏情况下为 O(N)。但这与我们已经分配的temp
列表是同一数量级。
综合分析:
算法所需的额外空间主要由 temp
列表决定。因此,其空间复杂度为 O(N)。这不满足题目通常要求的 O(1) 空间复杂度(原地排序)。