第四题 Sort List
Sort a linked list in O(n log n) time using constant space complexity.
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
看到这个题时,当时感觉还挺兴奋。对链表排序,要求复杂度为O(nlogn)。
然后直接就用快速排序实现了一遍:
class Solution {
public:
int Partition(vector<int>&input, int low, int high)
{
int pivotkey = input[low];
while(low < high)
{
while((low < high) && (input[high] >= pivotkey))
{
high--;
}
int temp = input[high];
input[high] = input[low];
input[low] = temp;
while((low < high) && (input[low] <= pivotkey))
{
low++;
}
temp = input[high];
input[high] = input[low];
input[low] = temp;
}
return low;
}
void QuickSort(vector<int>&input, int low, int high)
{
int pivot = 0;
if(low < high)
{
pivot = Partition(input, low, high);
QuickSort(input, low, pivot - 1);
QuickSort(input, pivot + 1, high);
}
}
ListNode *sortList(ListNode *head) {
if (!head)
{
return NULL;
}
ListNode* pos = head;
vector<int>value;
while (pos)
{
int val = pos->val;
value.push_back(val);
pos = pos->next;
}
if (value.size() <= 1)
{
return head;
}
QuickSort(value, 0, value.size() - 1);
pos = head;
int position = 0;
while (pos)
{
pos->val = value[position];
position++;
pos = pos->next;
}
return head;
}
};
结果嘛,可想而知---
Time Limit Exceeded
然后瞬间醒悟过来了,快速排序最慢的时候复杂度是O(n²)啊。不过还是重温了一下快速排序的过程:定位+递归。
定位:就是定位所选枢轴的位置,每次定位的话(升序排序),都能保证比枢轴小的元素在枢轴的左边,比枢轴大的元素在枢轴的右边。
递归:定位好当前位置,可以对枢轴的左边和枢轴的右边分别采用相同的方式继续定位和递归。
因为对链表排序,链表中的元素不支持随机访问所以我先将链表元素存到了Vector中。
然后就想到了堆排序(已AC),因为堆排序的时间复杂度是O(nlogn)。为了方便起见,Vector中首元素存了一个0,其他元素从下标1开始。
class Solution {
public:
void AdjustHeapItem(vector<int>&input, int start, int end)
{
int temp = input[start];
for(int i = 2*start; i <= end; i *= 2)
{
if (i < end && input[i] < input[i+1])
{
i++;
}
if (temp >= input[i] )
{
break;
}
input[start] = input[i];
start = i;
}
input[start] = temp;
}
void HeapSort(vector<int>&input)
{
int length = (input.size() - 1);
for(int i = length/2; i > 0; i--)
{
AdjustHeapItem(input, i, length);
}
for (int i = length; i > 1; i--)
{
int temp = input[1];
input[1] = input[i];
input[i] = temp;
AdjustHeapItem(input, 1, i-1);
}
}
ListNode *sortList(ListNode *head) {
if (!head)
{
return NULL;
}
ListNode* pos = head;
vector<int>value;
value.push_back(0);
while (pos)
{
int val = pos->val;
value.push_back(val);
pos = pos->next;
}
if (value.size() <= 2)
{
return head;
}
HeapSort(value);
pos = head;
int position = 1;
while (pos)
{
pos->val = value[position];
position++;
pos = pos->next;
}
return head;
}
};
堆排序的过程也是两部分:调整堆 + 交换元素。大顶堆的性质为K[i]>K[2i]&&K[i]>K[2i+1],本题使用大顶堆,因为排序完了正好是从小到大。
首次使用的时候相当于创建堆,接下来是交换堆中第一个元素与最堆中最后一个元素的位置,然后分离出最后一个元素,重新调整堆,然后再交换,再分离,再调整。。。直到最后一个元素为止。
借这个题重新回忆下各种排序的时间复杂度:
冒泡排序: 平均复杂度O(n
²
), 最好O(n),最坏O(n²), 空间复杂度O(1), 稳定
简单选择排序: 平均复杂度O(n
²
), 最好O(n²),最坏O(n²), 空间复杂度O(1), 稳定
直接插入排序:
平均复杂度O(n
²
), 最好O(n),最坏O(n²), 空间复杂度O(1), 稳定
希尔排序: 平均复杂度O(nlogn)~O(n
²
)(平时基本没用过)
堆排序:平均复杂度O(nlogn),最好O(nlogn),最坏O(nlogn), 空间复杂度O(1), 不稳定
归并排序:平均复杂度O(nlogn),最好O(nlogn),最坏O(nlogn), 空间复杂度O(n), 稳定
快速排序:
平均复杂度O(nlogn),
最好O(nlogn),最坏O(n²), 空间复杂度O(logn)~O(n)(递归时要压栈), 不稳定
所以这个题用归并排序也能符合题目要求,有时间再实现一下。