- Sort List
Given the head of a linked list, return the list after sorting it in ascending order.
Follow up: Can you sort the linked list in O(n logn) time and O(1) memory (i.e. constant space)?
这里给出了三个测试用例
初一看这个题可能觉得没什么,也就是个单链表排序问题,于是我就傻乎乎的写出了这样的解法(C++实现的常规暴力解法)
ListNode* sortList(ListNode* head) {
//头插法排序
if(head == NULL) return NULL;
ListNode *root = new ListNode();
ListNode *s = head->next,*pre,*q;
head -> next = NULL;
root->next = head;
pre = root;
while(s != NULL){
ListNode *tmp = root -> next;
while(tmp != NULL && s != NULL && s->val > tmp->val){
pre = tmp;
tmp = tmp -> next;
}
// 直到找到List中大于s->val的
pre -> next = s;
q = s -> next;
s -> next = tmp;
s = q;
pre = root;
}
return root->next;
}
};
对上述解法的简单总结就是,建立一个空链表root,然后每次从head链表上拿走一个结点插入到root中,至于插入到root中的哪个位置,则是根据内层while循环进行搜索,直到找到第一个比它大的结点然后停止。就可以三部曲插入结点啦!然鹅,Medium的题并没有那么简单,程序运行超时。而且我留意到起初的那句话,时间复杂度O(nlogn),空间复杂度O(1)。我是一个都没有做到呀,惭愧惭愧,立马跑去Discuss区剽窃学习大佬的思路。
经过耐心的分析和理解,终于把没有注释的神仙级代码看懂了,必须在此做个记录,方便回顾学习!
原文在这里,用到了递归和分治,for循环中的while循环一共执行logn次(因为每次都是跳着移动长度为n的列表),因此整体复杂度为O(nlogn)。核心思想就是每次把相邻的两个子列表(长度一样,依次为2,4,8等等)进行按序合并,最后直到整个列表都有序。
- 例如第一轮,先把长度为n的list划分为多个长度为2的小列表,暂时标记为l1,l2,l3,l4…,然后将l1与l2顺序合并,l3与l4顺序合并,以此类推。
- 第二轮每次将长度为4的小列表合并,同理最后将两个有序的长为length/2的列表合并(同归并排序的思路一样!!!)
其他的细节我就把写到注释里,大家看看哈,如果还有不明白的地方请评论,我经常在CSDN的哈哈哈
ListNode* sortList(ListNode* head) {
// 解决双层while循环的超时问题
int length = 0; // 记录链表的长度
ListNode *tmp = head; //临时指针,辅助计算链表长度
while(tmp!=NULL){
length++;
tmp = tmp->next;
}
// 得到链表长度
ListNode dummy(0); // 还是构建一个新的列表
ListNode *left, *right,*tail;
dummy.next = head;
for(int step = 1;step < length;step<<=1){ //i安装2,4,8,16的方式扩大
ListNode* cur = dummy.next;
tail = &dummy; //取dummy的地址
while(cur != NULL){
left = cur;
right = split(left, step);//从left结点的位置开始,向右移step个位置
cur = split(right, step); //从right结点的位置开始,向右移step个位置
//经过上面的两次移动,共从head移动了两个step的距离,这就是循环指针每次*2的原因
//下面合并两个子列表,并且返回列表尾指针用于连接下一层
tail = merge(left, right, tail);
}
}
return dummy.next;
}
// 列表后移n下
ListNode* split(ListNode* head, int n){
for(int i = 1; head && i < n;i++) head = head -> next;
if(head == NULL) return NULL;
ListNode* second = head -> next;
head -> next = NULL;
return second;
}
// 合并两个子列表,分别表示前半个列表和后半个列表的首个结点
ListNode* merge(ListNode* l1, ListNode* l2, ListNode* head){
ListNode *tmp = head;
while(l1&&l2){
if(l1->val < l2->val){
tmp->next = l1;
tmp = tmp -> next;
l1 = l1->next;
}else{
tmp->next = l2;
tmp = tmp -> next;
l2 = l2 -> next;
}
}
tmp -> next = l1? l1:l2;
while(tmp->next) tmp = tmp->next;
return tmp;
}