例题:148. 排序链表
网址:https://leetcode-cn.com/problems/sort-list/
前沿:这个题目既是链表排序的题目也是常规排序算法的考察,常规解法应该考虑的是插入排序,但是时间复杂度O(n^2)。如果使得时间复杂度降低到O(nlogn)甚至空间复杂度降至O(1)那么只能采用归并排序,堆排序,快速排序(最坏情况也是n*n)。考虑到这题数据存储考虑的是链表数据采用归并排序。归并排序分为自顶向下和自底向上两种,第一种需要使用栈,空间复杂度O(logn),第二种空间复杂度则是O(1)。
方法1:
/**
* 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) {
//插入排序
ListNode* Head = new ListNode(0);
Head->next = nullptr;
ListNode* current = Head;
ListNode* p = Head->next;
while(head!=nullptr){
p = head;
head = head->next;
current = Head; //每次需要从头开始比较
while(current->next!=nullptr && current->next->val<p->val){
current = current->next;
}
p->next = current->next;
current->next = p;
}
return Head->next;
}
};
注意点:时间复杂度过高,LedeCode并过不了,而且需要注意的是:1.每次遍历记得更新当前节点,为了避免闭环,Head必须是一个新的开始节点,避免和前面节点起冲突。
方法2(自顶向下的归并排序):
/**
* 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) {
//归并排序
return sortL(head, nullptr);
}
ListNode* sortL(ListNode* head, ListNode* tail){
if(head == tail) return head;
if(head->next == tail){
head->next = nullptr;
return head;
}
//寻找中间节点
ListNode* slow = head;
ListNode* fast = head;
while(fast!=tail){
fast = fast->next;
slow = slow->next;
if(fast!=tail){
fast=fast->next;
}
}
ListNode* mid = slow;
return merge(sortL(head, mid), sortL(mid, tail));
}
ListNode* merge(ListNode* head1, ListNode* head2){
ListNode* Head = new ListNode(0);
ListNode* tmp = Head;
Head->next = nullptr;
while(head1!=nullptr && head2!=nullptr){
if(head1->val<=head2->val){
tmp->next = head1;
head1 = head1->next;
}else{
tmp->next = head2;
head2 = head2->next;
}
tmp = tmp->next;
}
if(head1!=nullptr) tmp->next = head1;
if(head2!=nullptr) tmp->next = head2;
return Head->next;
}
};
注意点:1.寻找中间点使用的是快慢指针;2.迭代的时候注意处理节点的中间边界,也就是当子序列长度为2的时候注意保留第一个部分,子序列的最后一个节点总是用来废弃的;3.合并排序的注意对于采用头指针方式更好写。
方法3(自底向上的归并排序)
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (head == nullptr) {
return head;
}
int n = 0;
ListNode* p = head;
while(p!=nullptr){
n++;
p = p->next;
}
ListNode* Head = new ListNode(0, head);
for(int sublength=1;sublength<n;sublength<<=1){
ListNode* curr = Head->next;
ListNode* pre = Head; //和插入排序类似每次需要考虑从头开始的递归
while(curr!=nullptr){
ListNode* head1 = curr;
for(int i=1;i<sublength&& curr->next!=nullptr;i++){
curr = curr->next;
}
ListNode* head2 = curr->next;
curr->next = nullptr;
curr = head2;
for(int i=1;i<sublength&& curr!=nullptr && curr->next!=nullptr;i++){ //这一块的curr!=nullptr,一定要注意
curr = curr->next;
}
ListNode* next = nullptr;
if (curr != nullptr) {
next = curr->next;
curr->next = nullptr;
}
pre->next = merge(head1, head2);
while(pre->next!=nullptr){
pre = pre->next;
}
curr = next;
}
}
return Head->next;
}
ListNode* merge(ListNode* head1, ListNode* head2) {
ListNode* dummyHead = new ListNode(0);
ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
while (temp1 != nullptr && temp2 != nullptr) {
if (temp1->val <= temp2->val) {
temp->next = temp1;
temp1 = temp1->next;
} else {
temp->next = temp2;
temp2 = temp2->next;
}
temp = temp->next;
}
if (temp1 != nullptr) {
temp->next = temp1;
} else if (temp2 != nullptr) {
temp->next = temp2;
}
return dummyHead->next;
}
};
注意点:一个是归并排序自底向上的实现通过长度的来实现循环,另一个是需要考虑针对后一个序列的指针迭代要考虑到是否处理指针空指针