题目介绍
力扣148题:leetcode-cn.com/problems/so…
方法一:优先队列
该题目的解题思路跟[23. 合并K个升序链表]类似,可是使用优先队列,把链表节点放入一个最小堆,就可以每次获得链表中的的最小节点,依次使用哑结点连接起来即可,代码如下:
/**
* 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 sortList(ListNode head) {
//哨兵节点,指向新链表头节点的上一个节点
ListNode preHead = new ListNode(-1);
ListNode pre = preHead;
//创建优先队列,使用小顶堆
PriorityQueue<ListNode> pq = new PriorityQueue<>((a,b) -> {
return a.val - b.val;
});
ListNode temp = head;
while(temp != null) {
//加入优先队列
pq.add(temp);
temp = temp.next;
}
while(!pq.isEmpty()) {
//获取小顶堆最小值,即堆顶元素
ListNode node = pq.poll();
pre.next = node;
//防止循环链表
if(pq.size() == 0) {
node.next = null;
}
//pre指针不断前进
pre = pre.next;
}
return preHead.next;
}
}
方法二:自顶向下归并排序
归并排序其实就是二叉树后续遍历,我们常见的对数组的归并排序代码如下:
public class MergetSort {
public static void main(String[] args) {
int arr[] = {8, 4, 7, 5};
int temp[] = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println("归并排序后= " + Arrays.toString(arr));
}
//先分再治
//分
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
//左递归
mergeSort(arr, left, mid, temp);
//右递归
mergeSort(arr, mid + 1, right, temp);
//排序
merge(arr, left, mid, right, temp);
}
}
//合并
//治
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
int t = 0;
//将左边的数与右边的数进行比较
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
} else {
temp[t] = arr[j];
t += 1;
j += 1;
}
}
while (i <= mid) {
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) {
temp[t] = arr[j];
t += 1;
j += 1;
}
//将temp中的数据拷贝到arr中
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
对链表自顶向下归并排序的过程如下。
找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
对两个子链表分别排序。将两个排序后的子链表合并,得到完整的排序后的链表。可以使用「21. 合并两个有序链表」的做法,将两个有序的子链表进行合并。
上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 1 个节点时,不需要对链表进行拆分和排序。
代码如下:
/**
* 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 sortList(ListNode head) {
return sortList(head, null);
}
public ListNode sortList(ListNode head, ListNode tail) {
//为空或者只有一个元素,直接返回
if (head == null || head.next == null) {
return head;
}
//只有两个元素 类似于 4->2 直接断开,返回4,2,然后进行merge排序合并
if (head.next == tail) {
head.next = null;
return head;
}
ListNode slow = head, fast = head;
while (fast != tail) {
slow = slow.next;
fast = fast.next;
if (fast != tail) {
fast = fast.next;
}
}
ListNode mid = slow;
ListNode list1 = sortList(head, mid);
ListNode list2 = sortList(mid, tail);
ListNode sorted = merge(list1, list2);
return sorted;
}
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummyHead = new ListNode(0);
ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
while (temp1 != null && temp2 != null) {
if (temp1.val <= temp2.val) {
temp.next = temp1;
temp1 = temp1.next;
} else {
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if (temp1 != null) {
temp.next = temp1;
} else if (temp2 != null) {
temp.next = temp2;
}
return dummyHead.next;
}
}
复杂度分析
-
时间复杂度: O(nlogn),其中 n 是链表的长度。
-
空间复杂度: O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。