Sort List 题目描述
Sort a linked list in O(n log n) time using constant space complexity.
Example 1:
Input: 4->2->1->3
Output: 1->2->3->4
Example 2:
Input: -1->5->3->4->0
Output: -1->0->3->4->5
即链表数据结构下的排序,且题目要求时间复杂度在O(n log n)以内,空间复杂度为常数O(1)。
思路解析:
分析上述问题,要解决的有两点:
1. 链表数据的排序,而不是熟悉的数组排序;
2. 时间复杂度控制在O(n log n)以内,即需要熟悉常用的几种排序方式;
3. 空间复杂度为常数,即O(1),不允许使用额外的空间。
首先是链表排序,不能像数组一样随意的访问元素,链表需要从头往后撸。其次时间复杂度,常用的冒泡排序、选择排序、插入排序的时间复杂度为O (n^2),不满足要求。快速排序平均时间复杂度为O(n logn),最差为O(n^2),也不合适。再来看归并排序,时间复杂度为O(n log n),满足要求。空间复杂度,数组下的归并排序为O(n),即建立一个数组来存放排好的数。但在链表数据结构下,空间复杂度为O(1)。因为链表的节点从一开始给定后就不在变更,在排序过程中只是每个节点指向位置不同,即指针变化,而节点数不变。
通过上述分析,用归并排序解题。归并排序的核心思想就是找到数组,将原数组分开并递归此步骤,直到只有1个数时,开始比较排序并合并,即归并。在链表中也类似,从head节点往后撸,找到中间节点并把链表一分为二,递归此步骤。方法是撸链表时,设置快速指针fast和慢速指针slow,fast跑两步slow跑一步,当fast跑的最后一个节点时,slow到达middle处。递归上述步骤,归并时将节点重新排列。
代码实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(!head || !head->next) return head;
// 找到列表中间指针
ListNode *fast = head, *slow = head, *pre = head;
while (fast && fast->next) {
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = NULL;
// 递归找到子列表中间指针,直到剩下一个列表
ListNode* leftList = sortList(head);
ListNode* rightList = sortList(slow);
// 归并排序
return mergeSort(leftList, rightList);
}
ListNode* mergeSort(ListNode* leftList, ListNode* rightList) {
ListNode* result = new ListNode(-1);
ListNode* cur = result;
while (leftList || rightList) {
int valL = (leftList == NULL) ? INT_MAX : leftList->val;
int valR = (rightList == NULL) ? INT_MAX : rightList->val;
if (valL <= valR) {
cur->next = leftList;
leftList = leftList->next;
}
else {
cur->next = rightList;
rightList = rightList->next;
}
cur = cur->next;
}
return result->next;
}
};
学习内容:
1. 链表数据结构的再一次熟悉,如无new ListNode()代码便不生成新节点,没有额外空间。当使用ListNode* p创建新的链表指针时,并没有建立新节点没有增加额外空间,只是为了改变已有节点的指向。所以上述代码空间复杂度为O(1)。
2. 归并排序算法在链表数据结构中的使用