时间复杂度要求O(nlogn),容易让人想到归并或者快排,如果把数据导入到数组里,进行排序很容易实现,但是空间复杂度是O(n),不符合要求。
归并:
换个写法更加易读:
class Solution {
public:
ListNode* merge(ListNode *left, ListNode *right){
auto head = new ListNode;
auto h = head;
while(left && right){
if(left->val < right->val){
h->next = left;
left = left->next;
}else{
h->next = right;
right = right->next;
}
h = h->next;
}
h->next = left == NULL ? right : left;
return head->next;
}
ListNode* merge_sort(ListNode *head){
if(!head->next) return head;
ListNode *slow = head, *fast = head->next;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
}
auto r_head = slow->next;//slow->next右边部分的头结点
slow->next = NULL;//左边切断开
auto left = merge_sort(head);
auto right = merge_sort(r_head);
return merge(left, right);
}
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
return merge_sort(head);
}
};
不过这个代码的空间复杂度不知道算是O(1)还是O(logn),因为不知道递归栈要不要算进去。那递归的代码都可以写成非递归的,那就改成迭代的看看:
参考题解:
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
int n = 0;
for(auto i = head; i != NULL; i = i->next) ++n; //n为链表长度
auto dummy = new ListNode(-1);
dummy->next = head;
// 总体循环次数为logn
for(int i = 1; i < n; i += i){
auto beg = dummy;
// 最开始1个和1个合并为两个,然后2,2->4, 4,4->8
for(int j = 0; j + i < n; j += 2*i){ //j += 2*i为下一块的起点
auto left = beg->next;
auto right = left;
for(int k = 0; k < i; ++k) right = right->next;//right指向第二块的起点,每块有i个节点,越过i个节点即可
// merge第一块和第二块,起点分别为left, right
// 第一块的节点数为i, 第二块的节点数可能小于i(为i-1),因为节点个数有奇偶之分,所以需要检查right != NULL
int p = 0, q = 0;//计数
while(p < i && q < i && right != NULL){
if(left->val <= right->val){
beg->next = left;
left = left->next;
++p;
}else{
beg->next = right;
right = right->next;
++q;
}
beg = beg->next;
}
while(p < i){// 可能还有剩余未处理的
beg->next = left;
beg = beg->next;
left = left->next;
++p;
}
while(q < i && right != NULL){
beg->next = right;
beg = beg->next;
right = right->next; //right会指向下一块的起点
++q;
}
// 处理完之后beg指向的是两块中(已经排序好)元素最大的那个节点
beg->next = right; //调整beg位置,将beg和下一块连接起来
}
}
return dummy->next;
}
};
如果使用快速排序,如果可以交换链表的值的话,就会简单很多,而且空间复杂度为o(1):
class Solution {
public:
ListNode* partition(ListNode *head, ListNode *tail){
auto pivot = head->val;
auto left = head;
auto cur = head->next;
while(cur != tail){
if(cur->val < pivot){
left = left->next;
swap(left->val, cur->val);
}
cur = cur->next;
}
swap(head->val, left->val);
return left;
}
void quickSort(ListNode *head, ListNode *tail){//不包含tail
if(head == tail || head->next == tail) return; //head->next == tail表示区间内只有一个节点
auto q = partition(head, tail);
quickSort(head, q);
quickSort(q->next, tail);
}
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
quickSort(head, NULL);
return head;
}
};
如果不能交换链表的值的话,那可以根据pivot的值,将不同的节点接到不同的头(两种头,大于pivot,小于pivot)下面(相当于按照pivot分类),然后递归。最后进行相连。
class Solution {
public:
ListNode* quicksort(ListNode *head){
if(!head || !head->next) return head;
auto pivot = head->val;
auto left = new ListNode(-1); //左边部分的开头
auto right = new ListNode(-1); //右边部分的开头
ListNode *l = left,*r= right;
for(auto cur = head; cur != NULL; cur = cur->next){
if(cur->val < pivot){
l->next = cur;
l = l->next;
}else{
r->next = cur; //那么此处第一个就是head
r = r->next;
}
}
l->next = NULL;
r->next = NULL;
// 现在left->next即为左边部分的头结点
//right->next即为pivot
//riight->next->next即为右边部分的头结点
auto p = quicksort(left->next);
auto q = quicksort(right->next->next);
//上面分治的时候切断了, 最后需要再进行连接
//p可能为NULL
if(!p) left->next = right->next;
else{
left->next = p;
while(p->next != NULL) p = p->next; //p指向末尾
p->next = right->next; //right->next即为pivot
}
right->next->next = q;
return left->next;
}
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
return quicksort(head);
}
};