题目来源
题目描述
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) {
}
};
题目解析
常见排序方法有很多,插入排序,选择排序,堆排序,快速排序,冒泡排序,归并排序,桶排序等等。。它们的时间复杂度不尽相同,而这里题目限定了时间必须为O(nlgn),符合要求只有快速排序,归并排序,堆排序,而根据单链表的特点,最适合用归并排序。
为什么呢?这是由于链表不能随机访问,所以
- 快排实现会比较麻烦
- 堆排序的话,如果让新建结点的话,还是可以考虑的,若只能交换结点,最好还是不要用
- 归并排序因为其可以利用递归来交换数据,天然适合链表这种结构
归并排序
时间复杂度O(nlogn),不考虑递归栈空间的话空间复杂度是O(1),其数组实现可以参考[算法:归并排序]
归并排序的核心是一个merge()函数,其主要功能是实现两个有序链表的合并。
算法: 将链表从中间断开【快慢指针取中】,分成连部分,左右两边在分别分开。。。一直到只剩下一个节点【这个时候就没有方法在拆分了】,然后就可以merge了
cpp
class Solution {
ListNode *merge(ListNode *l1, ListNode *l2){
ListNode *dummy = new ListNode(-1);
ListNode *move = dummy;
while (l1 && l2){
if(l1->val < l2->val){
move->next = l1;
l1 = l1->next;
}else{
move->next = l2;
l2 = l2->next;
}
move = move->next;
}
if(l1){
move->next = l1;
}else{
move->next = l2;
}
return dummy->next;
}
public:
ListNode* sortList(ListNode* head){
if(!head || !head->next){
return head;
}
ListNode *slow = head, *fast = head, *pre = nullptr;
while (fast != NULL && fast->next != NULL){
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = NULL;
//因为快指针每次走两步,慢指针每次走一步,当快指针到达链表末尾时,慢指针正好走到中间位置,参见代码如下:
return merge(sortList(head), sortList(slow));
}
};
选择排序
- 开始时默认整个链表都是未排序部分
- 每次在未排序部分找到最小值的节点,并把这个节点从未排序链表中删除,删除的过程中需要保存不断链。比如:2->1->3,变成2->3,并返回要删除链表的前一个节点
- 把删除的节点连接到排好序部分的链表尾部
class Solution {
ListNode *getSmallestPreNode(ListNode*head){
ListNode *smallPre = nullptr, *small = head;
ListNode *prev = head, *curr = head->next;
while (curr != nullptr){ // 是否还有比small更小的节点
if(curr->val < small->val){
smallPre = prev;
small = curr;
}
prev = curr;
curr = curr->next;
}
return smallPre;
}
public:
ListNode* sortList(ListNode* head) {
if(head == nullptr || head->next == nullptr){
return nullptr;
}
ListNode *tail = nullptr;// 排序部分尾部(刚开始是没有有序部分)
ListNode *curr = head; // 未排序部分头部(开始时默认整个链表都是未排序部分)
ListNode *smallPre = nullptr; // 最小节点的前一个节点
ListNode *small = nullptr; // 最小节点
while (curr != nullptr){// 还有未排序部分
small = curr;
smallPre = getSmallestPreNode(curr); // 去未排序部分找最小的节点
// 将这个最小值从当前链表中删除
if(smallPre != nullptr){ // curr不是最小值
small = smallPre->next;
smallPre->next = small->next;
}
curr = curr == small ? curr->next : small;
//将small加到tail的尾部
if(tail == nullptr){
head = small;
tail = small;
}else{
tail->next = small;
tail = small;
}
}
return head;
}
};
冒泡排序
- 冒泡排序是两两相邻比较的
时间复杂度O(n^2),空间复杂度O(1)
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head == nullptr || head->next == nullptr){
return nullptr;
}
ListNode *cur = head;
ListNode *tail = nullptr; // 初始时:链表的最后一个节点指向null
bool flag;
while (cur != tail){
flag = false;
while (cur->next != tail){ // 无序区还有节点可以比较
if (cur->val > cur->next->val){ // 当前节点和当前节点的下一个节点比较
std::swap(cur->val, cur->next->val);
flag = true;
}
cur = cur->next;
}
if (!flag){ // 经过一轮比较没有泡泡
break;
}
// 跳出循环时:cur.next = null,cur是链表的最后一个节点
tail = cur; // 第一次循环后,尾巴节点前面移动
cur = head;
}
return head;
}
};
梳子排序是冒泡排序的改进,它需要跨索引比较,而链表必须一个个访问,因此pass
地精排序需要来回冒泡,也就是说要回退链表,而链表回退是很难的
奇偶排序
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head == nullptr || head->next == nullptr){
return nullptr;
}
bool flag = true; //默认当前链表是需要比较的
while (flag){
flag = false; // 默认已经比较完成了
// 先进行奇数位排序
for (ListNode *cur = head; cur != nullptr && cur->next != nullptr ; cur = cur->next->next){
if (cur->val > cur->next->val){
std::swap(cur->val, cur->next->val);
flag = true;
}
}
// 偶数位的排序
for (ListNode *cur = head->next; cur != nullptr && cur->next != nullptr ; cur = cur->next->next){
if (cur->val > cur->next->val){
std::swap(cur->val, cur->next->val);
flag = true;
}
}
}
return head;
}
};
计数排序&&桶排序
因为不知道链表的最大值和最小值,也就不知道要分配多大的桶,这是不现实的,因此pass
类似题目
题目 | 核心思路 |
---|---|
leetcode:21. 合并两个有序链表 Merge Two Sorted Lists | 比较当前两个节点值大小,然后移动较小的那个 |
leetcode:23. 合并K个升序链表 Merge k Sorted Lists | 逐一合并两条链表;分治合并;k指针;优先队列(将链表的值存入小根堆中,再逐次将堆顶取出连接成链表) |
leetcode:148. 排序链表 Sort List | 归并排序:将链表从中间断开【快慢指针取中】,分成连部分,左右两边在分别分开。。。一直到只剩下一个节点【这个时候就没有方法在拆分了】,然后就可以merge了 |
leetcode:147. 对链表进行插入排序 Insertion Sort List | |
leetcode:708. 循环有序列表的插入 Insert into a Cyclic Sorted List | |
leetcode:75. 颜色排序 Sort Colors |