问题描述:
思路:
拿之前那道“对链表插入排序”的代码可以直接通过,但是不符合题目的要求:时间复杂度控制在O(nlogn),插入排序时间复杂度在o(n^2),又因为此时是对链表不是数组,所以考虑到使用归并排序。
归并的两种解法:自顶向下递归和自底向上的迭代,其中递归法也不满足题目要求的o(1)级别的空间复杂度,故只有迭代才是满足题意的,对于递归的归并,书写代码难度更低,迭代实则是模拟一边全过程要考虑的边界情况都比较多细节可能出错的地方也很多。
下面找到两幅图来说明递归和迭代法:
对于递归来说,只需要找到其终止条件和对于子单位的操作,此处终止条件就是(当前节点是否为空 || 当前节点的next是否为空),即分到只有一个节点或传入参数为空的情况;子单元的操作就是对其进行一次中点断链切分为两部分,然后对两部分进行合并操作。断链是在链表中点断链(对于中点的定义为:奇数个节点就在len/2处,偶数个节点在len/2 - 1处),寻找中点的过程用到了快慢指针;合并操作类似于合并两个原本有序的链表要求合并后的链表依然有序,这一操作思路也很简单的。
对于迭代法来说,关键在于把握住循环的终止条件,以及在每一intv轮合并时,找好h1和h2的位置,需要注意如果h2为NULL那么可以直接结束本次merge,之后就是比较多的细节要注意。
代码:
法一:
// 对链表进行自顶向下的归并排序(能通过,但不满足题目对空间复杂度的要求)
ListNode* sortList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
ListNode *fast, *slow;
slow = head;
fast = head->next;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
ListNode *temp = slow->next;
slow->next = NULL;
ListNode *left = sortList(head);
ListNode *right = sortList(temp);
// 合并left和right两个有序链表
ListNode* new_head = (ListNode *)malloc(sizeof(ListNode));
ListNode* p = new_head;
while (left != NULL && right != NULL) {
if (left->val < right->val) {
p->next = left;
p = p->next;
left = left->next;
}
else {
p->next = right;
p = p->next;
right = right->next;
}
}
p->next = (left == NULL) ? (right) : (left);
return new_head->next;
}
法二:
// 自底向上规定排序 (迭代)
ListNode* sortList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
int len = 0, intv = 1;
ListNode* p = head;
while (p != NULL) {
len++;
p = p->next;
}
ListNode* new_head = (ListNode*)malloc(sizeof(ListNode));
new_head->next = head;
while (intv < len) {
ListNode* pre = new_head;
ListNode* h = new_head->next;
while (h != NULL) {
ListNode *h1 = h; // 第一部分的头
int i = intv;
ListNode *h2 = h1;
while (h2 != NULL && i != 0) {
h2 = h2->next; // 第二部分的头
-- i;
}
if (i != 0) { // 还没找到h2 就走到头了 说明没有h2了,无须merge了
break;
}
// 获取h1、h2部分的长度(h1不用再算了,因为如果h2存在那么h1肯定是完整的intv长度
int c1 = intv;
ListNode* travelH2 = h2;
i = intv;
while (travelH2 != NULL && i != 0) {
travelH2 = travelH2->next;
-- i;
}
h = travelH2; // h指向h2的末尾的下一个元素 即下两单位合并的开始部分
int c2 = intv - i;
// merge h1和h2部分
while (c1 != 0 && c2 != 0) {
if (h1->val < h2->val) {
pre->next = h1;
h1 = h1->next;
--c1;
}
else {
pre->next = h2;
h2 = h2->next;
--c2;
}
pre = pre->next;
}
if (c1 == 0) pre->next = h2;
else pre->next = h1;
while (c1 > 0 || c2 > 0) {
pre = pre->next;
--c1;
--c2;
}
pre->next = h;
}
intv *= 2;
}
return new_head->next;
}