[leetcode] 142.Sort List
2015-08-28 12:52
253 查看
题目:
Sort a linked list in O(n log n) time using constant space complexity.
题意:
对一个链表进行排序,时间复杂度要求是O(nlgn),常量空间复杂度。
思路:
对于排序算法,时间复杂度是O(nlgn)的算法有快排,堆排序,归并排序。当使用数组的时候归并排序需要O(n)的空间复杂度,但是对于链表我们可以实现两个有序数组的合并,可以保证O(1)的空间复杂度。
所以我们首先使用归并排序来完成。这个排序是稳定的,并且时间复杂度最差跟平均都是O(nlgn)。其中我们把各个功能分开实现, ListNode* splitList(ListNode* head)函数是用来将链表分成大小相等的两个部分,通过快慢两个指针,慢指针每次往前走一步,快指针每次往前走两步。 ListNode* mergeSortedList(ListNode* head1, ListNode* head2)函数是用来将两个有序链表进行合并。mergeSort(ListNode* head)是归并排序算法,会调用上面的两个函数。
代码如下:
2.我们还可以使用快排来完成。但是快排的性能不是稳定的,其最坏情况时间复杂度会变成O(n^2)。
另外我们还需要考虑数组中会出现很多重复元素的情况,如果使用最原始的快排,不加修改,那么会导致出现重复元素时性能很差。当某个区间内元素全部相同的时候,算法应该停止继续分割,因为这一段已经是排序的了。我们以第一个元素作为基准元素,first指针此时指向第一个元素。如果某个元素比该元素小,让first后移,并且与当前元素进行交换。扫描结束的时候,将first与基准元素交换。为什么我们是要将小于基准的放入前面而不是小于等于的放入前面呢?举个例子,比如数组是1,-3,-4,1,那么以1为基准的话,如果小于等于的都放在前面,那么扫描到最后时仍是1,-3,-4,1,此时first指向最后的那个1,与基准元素交换后仍然时1,-3,-4,1。所以会陷入死循环。我们使用小于号,这样会在这次partition后得到,-3,-4,1以及1这两部分。我们还需要处理元素相同的情况。
以上。
代码如下:
3.当然,我们还可以使用堆排序来完成啊!这里我们就是用STL中的set来完成,使用红黑树来完成排序的操作。
代码如下:
总结:
以上三种代码,时间分别是:
60ms,580ms,100ms。可以看的出归并排序是最好的,快排在最坏情况下确实表现不佳。而堆排序由于需要构建树的过程,多花了些时间,但总体来说还是挺好的。
Sort a linked list in O(n log n) time using constant space complexity.
题意:
对一个链表进行排序,时间复杂度要求是O(nlgn),常量空间复杂度。
思路:
对于排序算法,时间复杂度是O(nlgn)的算法有快排,堆排序,归并排序。当使用数组的时候归并排序需要O(n)的空间复杂度,但是对于链表我们可以实现两个有序数组的合并,可以保证O(1)的空间复杂度。
所以我们首先使用归并排序来完成。这个排序是稳定的,并且时间复杂度最差跟平均都是O(nlgn)。其中我们把各个功能分开实现, ListNode* splitList(ListNode* head)函数是用来将链表分成大小相等的两个部分,通过快慢两个指针,慢指针每次往前走一步,快指针每次往前走两步。 ListNode* mergeSortedList(ListNode* head1, ListNode* head2)函数是用来将两个有序链表进行合并。mergeSort(ListNode* head)是归并排序算法,会调用上面的两个函数。
代码如下:
/** 1. Definition for singly-linked list. 2. struct ListNode { 3. int val; 4. ListNode *next; 5. ListNode(int x) : val(x), next(NULL) {} 6. }; */ class Solution { public: ListNode* sortList(ListNode* head) { if(head == NULL || head->next == NULL)return head; return mergeSort(head); } ListNode* mergeSort(ListNode* head) { if(head == NULL || head->next == NULL)return head; if(head->next->next == NULL) { if(head->val > head->next->val){ swap(head->val, head->next->val); } return head; } ListNode* head2 = splitList(head); head = mergeSort(head); head2 = mergeSort(head2); return mergeSortedList(head, head2); } ListNode* splitList(ListNode* head) { ListNode* tmp = head; ListNode* tmp2 = head->next; ListNode* last = NULL; while(tmp2 != NULL && tmp2->next != NULL) { last = tmp; tmp = tmp->next; tmp2 = tmp2->next->next; } tmp = last; tmp2 = tmp->next; tmp->next = NULL; return tmp2; } ListNode* mergeSortedList(ListNode* head1, ListNode* head2) { ListNode* h = (head1->val < head2->val)?head1:head2; (head1->val < head2->val)?head1 = head1->next:head2 = head2->next; ListNode* current = h; while(head1 != NULL && head2 != NULL) { if(head1->val < head2->val) { current->next = head1; head1 = head1->next; } else { current->next = head2; head2 = head2->next; } current= current->next; } current->next = (head1 != NULL)?head1:head2; return h; } };
2.我们还可以使用快排来完成。但是快排的性能不是稳定的,其最坏情况时间复杂度会变成O(n^2)。
另外我们还需要考虑数组中会出现很多重复元素的情况,如果使用最原始的快排,不加修改,那么会导致出现重复元素时性能很差。当某个区间内元素全部相同的时候,算法应该停止继续分割,因为这一段已经是排序的了。我们以第一个元素作为基准元素,first指针此时指向第一个元素。如果某个元素比该元素小,让first后移,并且与当前元素进行交换。扫描结束的时候,将first与基准元素交换。为什么我们是要将小于基准的放入前面而不是小于等于的放入前面呢?举个例子,比如数组是1,-3,-4,1,那么以1为基准的话,如果小于等于的都放在前面,那么扫描到最后时仍是1,-3,-4,1,此时first指向最后的那个1,与基准元素交换后仍然时1,-3,-4,1。所以会陷入死循环。我们使用小于号,这样会在这次partition后得到,-3,-4,1以及1这两部分。我们还需要处理元素相同的情况。
以上。
代码如下:
class Solution { public: ListNode* sortList(ListNode* head) { if (head == NULL || head->next == NULL)return head; quickSort(head); return head; } void quickSort(ListNode* head) { if (head == NULL || head->next == NULL)return; bool all_same = true; ListNode* nextHead = partition(head, all_same); quickSort(head); if(!all_same)quickSort(nextHead); if (head == NULL)head = nextHead; else { ListNode* temp = head; while (temp->next != NULL) { temp = temp->next; } temp->next = nextHead; } } ListNode* partition(ListNode* head, bool& all_same) { ListNode* prev = head; ListNode* curr = head->next; while (curr != NULL) { if (all_same && curr->val != head->val)all_same = false; if (curr->val < head->val) { prev = prev->next; swap(prev->val, curr->val); } curr = curr->next; } swap(head->val, prev->val); ListNode* result = prev->next; prev->next = NULL; return result; } };
3.当然,我们还可以使用堆排序来完成啊!这里我们就是用STL中的set来完成,使用红黑树来完成排序的操作。
代码如下:
struct compareList{ bool operator()(ListNode* l1, ListNode* l2) { return l1->val < l2->val; } }comparelist; class Solution { public: ListNode* sortList(ListNode* head) { if (head == NULL || head->next == NULL)return head; return heapSort(head); } ListNode* heapSort(ListNode* head) { multiset<ListNode*, compareList> heap; while (head != NULL) { heap.insert(head); head = head->next; } ListNode* res= *(heap.begin()); ListNode* curr = res; auto iter = heap.begin(); iter++; while (iter != heap.end()) { curr->next = *iter; iter++; curr = curr->next; } curr->next = NULL; return res; } };
总结:
以上三种代码,时间分别是:
60ms,580ms,100ms。可以看的出归并排序是最好的,快排在最坏情况下确实表现不佳。而堆排序由于需要构建树的过程,多花了些时间,但总体来说还是挺好的。
相关文章推荐
- MATLAB2012 for linux的安装
- Thrift学习整理之环境搭建
- 问题集结(一)
- MySQL在大型网站的应用架构演变
- 什么是分布式数据库?
- asp.net 注册到IIS
- Java常用的设计模式01:设计模式的分类和原则
- Openstack 中的router创建,floatingip绑定
- cordova插件开发,简单教程
- 浅谈Java数据流
- iOS学习之CoreData的增删改查
- 时间的转换
- The function "state.highstate" is running as PID 4417的解决方法
- Android用ImageView显示本地和网上的图片
- Java之——Timer与ScheduledExecutorService
- 一次Linux系统被攻击的分析过程
- memcached协议
- Orders(C++ stl next_permutation)
- 创建控制器命令
- 自己写了oracle导入txt和csv格式的工具