一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情。
前言
力扣第148题 排序链表 如下所示:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入: head = [4,2,1,3]
输出: [1,2,3,4]
一、思路
题目很简短,与昨天的题目力扣第147题-对链表进行插入排序非常相似,都是将列表变为升序的排列。
如果直接用昨天的代码 插入排序 是可以通过这一题的。此外还有一个非常朴素的思路如下所示:
- 遍历链表,收集所有元素
- 将收集到的元素按照升序排列
- 再次遍历链表,依次取排序好的元素值,修改每一个元素
像这样也是可以完成这一题的,但是这需要 O(n) 的空间来存储各节点,这就不能满足题目的进阶要求了。
归并排序
那要怎么做呢?既然要求 O(N*logN) 的时间复杂度和常数级空间复杂度,很容易就能想到使用 归并排序。归并排序的核心思想就是:先拆分,再合并
此处就以示例中的head = [4,2,1,3] 作为例子来展示,拆分的原则是按照链表的中间拆的,一直拆分到子链表中只有两个元素(一个元素是无需重新排序的,所以拆分到两个元素就可以了)。
- 拆分的过程
链表最终会拆分成如下的两个子链表
- 合并的过程
合并的过程就是先将子链表中的元素排好序,再将排好序的子链表合并,也就是合并两个升序排序的链表。
综上所述,使用 归并排序 的步骤大致如下所示:
- 使用
递归来拆分链表 - 将子链表按升序排列
- 多个子链表间,两两合并升序排列的链表
二、实现
实现代码
实现代码与思路中保持一致
public ListNode sortList(ListNode head) {
return dfs(head, null);
}
public ListNode dfs(ListNode head, ListNode tail){
if (head == null)
return null;
if (head.next == tail){
head.next = null;
return head;
}
// 从中间分割
ListNode fast = head;
ListNode slow = head;
while (fast != tail){
slow = slow.next;
fast = fast.next;
if (fast != tail)
fast = fast.next;
}
ListNode listNode1 = dfs(head, slow);
ListNode listNode2 = dfs(slow, tail );
return mergeTwoLists(listNode1, listNode2);
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode ret = new ListNode();
ListNode temp = ret;
// 迭代
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
temp.next = l1;
l1 = l1.next;
} else {
temp.next =l2;
l2 = l2.next;
}
temp = temp.next;
}
// 将未遍历完的列表归入结果
if (l1 == null) {
temp.next = l2;
}
if (l2 == null)
temp.next = l1;
return ret.next;
}
测试代码
public static void main(String[] args) {
ListNode list = new ListNode(4, new ListNode(2, new ListNode(1, new ListNode(3))));
ListNode ret = new Number148().sortList(list);
System.out.println("test");
}
结果
三、总结
感谢看到最后,非常荣幸能够帮助到你~♥
如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~