#LeetCode每日一题【链表专题】
-
排序链表
https://leetcode-cn.com/problems/sort-list/ -
分析
链表经典排序算法,归并排序和快速排序;两者都是采用“分治”的思想,将整体不断的切分成两部分,直至最小。且时间复杂度都是O(n*logn),空间复杂度O(1)(不同于数组需要开辟新空间);
不同点是:归并排序是先切分再排序合并,快速排序是先排序在切分。 -
实现
归并排序:1. 使用递归不断的将链表分成两部分,直至长度为1(归) 2. 在切分结束之后,不断的对两个有序链表进行合并,使之有序(并)
快速排序:
1. 每次设定一个基准值,通过处理将左边的值都小于基准值、右边的值都大于基准值;如此的递归处理左右两部分 2. 处理完之后,整体自然是有序的
-
代码
归并排序:
/*
链表排序经典算法:归并排序
1. 不断地将链表拆分成两段,直至长度为1: 递归
2. 再对两个有序列表进行合并排序 合并
时间复杂度:O(n*logn) 空间复杂度 O(1)
*/
public ListNode sortList02(ListNode head) {
return merge(head);
}
private ListNode merge(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 获取链表中点、需要指定一个pre指针,对应慢指针的上一个
ListNode slow = head, fast = head, prev = null;
while (fast != null && fast.next != null) {
fast = fast.next.next;
prev = slow;
slow = slow.next;
}
prev.next = null;
ListNode left,right;
// 递归处理,不断地拆分成两段
left = merge(head);
right = merge(slow);
// 该递归函数需要返回 将左右排好序的新链表
return sort(left, right);
}
// 排序两个有序链表
private ListNode sort(ListNode left, ListNode right) {
// 排序两个有序链表
ListNode p = new ListNode(0);
ListNode pTemp = p;
while (left != null && right != null) {
if (left.val > right.val) {
pTemp.next = right;
right = right.next;
} else {
pTemp.next = left;
left = left.next;
}
pTemp = pTemp.next;
}
// 左右可能某个存在空
if (left != null) {
pTemp.next = left;
}
if (right != null) {
pTemp.next = right;
}
return p.next;
}
LeetCode耗时:5ms
快速排序:
/*
链表排序经典算法:快速排序
1. 每次设定一个基准值,通过处理将左边的值都小于基准值、右边的值都大于基准值;如此的递归处理左右两部分
2. 处理完之后,整体自然是有序的
时间复杂度:O(n*logn) 空间复杂度 O(1)
*/
public ListNode sortList04(ListNode head) {
quickSort(head, null);
return head;
}
/*
优化,不改变head指向,即递归程序返回值为null
添加一个参数,表示需要处理的链表区间
*/
private void quickSort(ListNode head, ListNode end) {
// 快速排序需要找到基准值的合适位置,即使得其左边值都比他小、右边值都比他大
// 在链表中使用快慢指针去寻找:慢指针留下来作为标记、快指针每次向前,当快指针小于基准值,交换快和慢的下一个指针位置(保证大的在后面,小的在前面),最后将基准值与慢指针位置进行交换
if (head == null || head.next == end) {
return;
}
ListNode slow = head, fast = head;
ListNode basic = head;
while (fast != end) {
if (fast.val < basic.val) {
slow = slow.next;
int temp = fast.val;
fast.val = slow.val;
slow.val = temp;
}
fast = fast.next;
}
if (slow != basic) {
// 交换slow和basic,此时即满足条件
int temp = slow.val;
slow.val = basic.val;
basic.val = temp;
}
// 右半部分
fast = slow.next;
quickSort(head, slow.next);
quickSort(fast, end);
}
快速排序另一种写法:(带返回值的)
/*
链表排序经典算法:快速排序
1. 每次设定一个基准值,通过处理将左边的值都小于基准值、右边的值都大于基准值;如此的递归处理左右两部分
2. 处理完之后,整体自然是有序的
时间复杂度:O(n*logn) 空间复杂度 O(1)
*/
public ListNode sortList03(ListNode head) {
return quickSort(head);
}
private ListNode quickSort(ListNode head) {
// 快速排序需要找到基准值的合适位置,即使得其左边值都比他小、右边值都比他大
// 在链表中使用快慢指针去寻找:慢指针留下来作为标记、快指针每次向前,当快指针小于基准值,交换快和慢的下一个指针位置(保证大的在后面,小的在前面),最后将基准值与慢指针位置进行交换
if (head == null || head.next == null) {
return head;
}
ListNode slow = head, fast = head;
ListNode basic = head;
while (fast != null) {
if (fast.val < basic.val) {
slow = slow.next;
int temp = fast.val;
fast.val = slow.val;
slow.val = temp;
}
fast = fast.next;
}
if (slow != basic) {
// 交换slow和basic,此时即满足条件
int temp = slow.val;
slow.val = basic.val;
basic.val = temp;
}
// 右半部分
fast = slow.next;
// 左半部分
slow.next = null;
ListNode left = quickSort(head);
ListNode right = quickSort(fast);
// 递归之后需要返回处理好的链表,需要首尾拼接下
// 也可以设计没有返回值的递归程序,即不改变head指向,多传一个参数标识一下位置即可;
ListNode t = left;
while (t.next != null) {
t = t.next;
}
t.next = right;
return left;
}
LeetCode耗时:1338ms
(有点震惊,比归并排序慢了几百倍…)