精选力扣500题 第43题 LeetCode 148. 排序链表c++/java详细题解
Posted 林深时不见鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了精选力扣500题 第43题 LeetCode 148. 排序链表c++/java详细题解相关的知识,希望对你有一定的参考价值。
1、题目
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
进阶:
- 你可以在 O ( n l o g n ) O(nlogn) O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目在范围
[0, 5 * 104]
内 -105 <= Node.val <= 105
2、思路
(归并排序) 时间: O ( n l o g n ) O(nlogn) O(nlogn) 空间: O ( 1 ) O(1) O(1)
自顶向下递归形式的归并排序,由于递归需要使用系统栈,递归的最大深度是
l
o
g
n
logn
logn,所以需要额外
O
(
l
o
g
n
)
O(logn)
O(logn) 的空间。
所以我们需要使用自底向上非递归形式的归并排序算法。
基本思路是这样的,总共迭代
l
o
g
n
logn
logn次:
- 第一次,将整个区间分成连续的若干段,每段长度是2: [ a 0 , a 1 ] , [ a 2 , a 3 ] , … [ a n − 1 , a n − 1 ] [ a 0 , a 1 ] , [a0,a1],[a2,a3],…[an−1,an−1][a0,a1], [a0,a1],[a2,a3],…[an−1,an−1][a0,a1],然后将每一段内排好序,小数在前,大数在后;
- 第二次,将整个区间分成连续的若干段,每段长度是4: [ a 0 , … , a 3 ] , [ a 4 , … , a 7 ] , … [ a n − 4 , … , a n − 1 ] [ a 0 , … , a 3 ] [a0,…,a3],[a4,…,a7],…[an−4,…,an−1][a0,…,a3] [a0,…,a3],[a4,…,a7],…[an−4,…,an−1][a0,…,a3],然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成;
- 依此类推,直到每段小区间的长度大于等于 n n n 为止;
另外,当
n
n
n 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。
举个例子:
根据图片可知,从底部往上逐渐进行排序,先将长度是1
的链表进行两两排序合并,再形成新的链表head
,再在新的链表的基础上将长度是2
的链表进行两两排序合并,再形成新的链表head
… 直到将长度是n / 2
的链表进行两两排序合并
step=1: (3->4) -> (1->7) -> (8->9) -> (2->11) -> (5->6)
step=2: (1->3->4->7) -> (2->8->9->11) -> (5->6)
step=4: (1->2->3->4->7->8->9->11) ->5->6
step=8: (1->2->3->4->5->6->7->8->9->11)
具体操作,当将长度是i
的链表两两排序合并时,新建一个虚拟头结点dummy
,[j,j + i - 1]
和[j + i, j + 2 * i - 1]
两个链表进行合并,在当前组中,p
指向的是当前合并的左边的链表,q
指向的是当前合并的右边的链表,o
指向的是下一组的开始位置,将左链表和右链表进行合并,加入到dummy
的链表中,操作完所有组后,返回dummy.next
链表给i * 2
的长度处理
注意的是:需要通过l
和r
记录当前组左链表和右链表使用了多少个元素,用的个数不能超过i
,即使长度不是 2n
也可以同样的操作
时间复杂度分析: 整个链表总共遍历 l o g n logn logn 次,每次遍历的复杂度是 O ( n ) O(n) O(n),所以总时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)。
空间复杂度分析: 整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O ( 1 ) O(1) O(1).
3、c++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
int n = 0;
for(auto p = head; p; p = p->next) n++;
auto dummy = new ListNode(-1); //虚拟头节点
dummy->next = head;
//每次归并段的长度,每次长度依次为1,2,4,8...n/2, 小于n是因为等于n时说明所有元素均归并完毕,大于n时同理
for(int i = 1; i < n; i *= 2)
{
auto cur = dummy ;
for(int j = 1; j + i <= n; j += 2*i ){ //j代表每一段的开始,每次将两段有序段归并为一个大的有序段,故而每次+2i //必须保证每段中间序号是小于链表长度的,显然,如果大于表长,就没有元素可以归并了
auto p = cur->next, q = p;//p表示第一段的起始点,q表示第二段的起始点,之后开始归并即可
for(int k = 0; k < i; k++) q = q->next;
//l,r用于计数第一段和第二段归并的节点个数,由于当链表长度非2的整数倍时表长会小于i,故而需要加上p && q的边界判断
int l = 0, r = 0;
while(l < i && r < i && p && q) //二路归并
{
if(p->val <= q->val) cur = cur->next = p, p = p->next, l++;
else cur = cur->next = q, q = q->next, r++;
}
while(l < i && p) cur = cur->next = p, p = p->next ,l++;
while(r < i && q) cur = cur->next = q, q = q->next ,r++;
cur->next = q;//记得把排好序的链表尾链接到下一链表的表头,循环完毕后q为下一链表表头
}
}
return dummy->next;
}
};
4、java代码
/**
* 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) {
int n = 0;
for(ListNode p = head; p!=null; p = p.next) n++;
ListNode dummy = new ListNode(-1); //虚拟头节点
dummy.next = head;
//每次归并段的长度,每次长度依次为1,2,4,8...n/2, 小于n是因为等于n时说明所有元素均归并完毕,大于n时同理
for(int i = 1; i < n; i *= 2)
{
ListNode cur = dummy ;
for(int j = 1; j+i <= n; j += 2*i ){ //j代表每一段的开始,每次将两段有序段归并为一个大的有序段,故而每次+2i //必须保证每段中间序号是小于链表长度的,显然,如果大于表长,就没有元素可以归并了
ListNode p = cur.next;
ListNode q = p;//p表示第一段的起始点,q表示第二段的起始点,之后开始归并即可
for(int k = 0; k < i; k++) q = q.next;
//l,r用于计数第一段和第二段归并的节点个数,由于当链表长度非2的整数倍时表长会小于i,故而需要加上p && q的边界判断
int l = 0, r = 0;
while(l < i && r < i && p!=null && q!=null) //二路归并
{
if(p.val <= q.val)
{
cur = cur.next = p;
p = p.next;
l++;
}
else
{
cur = cur.next = q;
q = q.next;
r++;
}
}
while(l < i && p!=null) {cur = cur.next = p;p = p.next ;l++;}
while(r < i && q!=null) { cur = cur.next = q; q = q.next ;r++;}
cur.next = q;
}
}
return dummy.next;
}
}
原题链接: 148. 排序链表
以上是关于精选力扣500题 第43题 LeetCode 148. 排序链表c++/java详细题解的主要内容,如果未能解决你的问题,请参考以下文章
精选力扣500题 第38题 LeetCode 300.长递增子序列c++/java详细题解
精选力扣500题 第35题 LeetCode 94. 二叉树的中序遍历c++ / java 详细题解
精选力扣500题 第55题 LeetCode 144. 二叉树的前序遍历c++/java详细题解
精选力扣500题 第52题 LeetCode 98. 验证二叉搜索树c++/java详细题解