NC70 单链表的排序
题意
给定一个无序单链表,实现单链表的排序(按升序排序)。
1. 值排序
这是一个偷懒的做法,我们可以遍历整个链表,把值放到数组中,再sort,再重新填进去。
class Solution { public: /** * * @param head ListNode类 the head node * @return ListNode类 */ ListNode* sortInList(ListNode* head) { vector<int> nums; ListNode* temp = head; // 遍历一遍链表 while (temp) { nums.push_back(temp->val); temp = temp->next; } // 对值排序 sort(nums.begin(), nums.end()); // 重新填回去 temp = head; for (int i = 0; i < nums.size(); i++) { temp->val = nums[i]; temp = temp->next; } return head; } };
- 时间复杂度:, 依赖于 STL sort的时间复杂度
- 空间复杂度:, 定义了长度为的链表nums。
2. 归并排序
我们看单链表的性质,有去无回, 那么我们可以利用归并排序的思路,先找到单链表的中点,再递归排序左右两侧,再合并。具体实现思路如下:(这个思路很重要,是两个单链表基础算法的融合)
2.1 寻找单链表中点
这是归并排序中的一个子问题,也同样是单链表中比较重要的基础算法。与 NC69 链表中倒数最后k个结点 类似,也是采用快慢指针法。
- 定义两个指针,较快的每次走两步,较慢的每次走一步
- 快指针走到头的时候,慢指针指向中点。
- 对左右两半递归调用排序算法。
2.2 合并两个有序链表为一个有序链表
双指针法,先定义辅助变量pHead,指向新链表头部。接下来如下操作:
- 定义两个指针i和j,分别指向两个有序链表的头部。
- 如果i的值更小,则i右移,新节点串入pHead右侧,否则j右移。
- 最后,i和j哪个有后继节点,就把哪个直接串入pHead右侧。
算法流程如下图所示。
class Solution { public: /** * * @param head ListNode类 the head node * @return ListNode类 */ // C++11中允许使用using关键字定义别名 using PNode = ListNode*; ListNode* sortInList(ListNode* head) { if (!head || !head->next) return head; // 2.1 快慢指针找中点 PNode fast = head->next, slow = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } // 这里需要注意,实现的时候需要把左半边“封口”,并缓存下右半边的起点 PNode temp = slow->next; slow->next = nullptr; // 递归两边 PNode left = sortInList(head), right = sortInList(temp); // 2.2 合并两个有序单链表为一个 // 这里需要设立一个虚拟节点(dummy head),否则不知道头在哪,返回虚节点的next即可 PNode dh = new ListNode(0), cur = dh; //cur指针从dummy head开始 // 合并过程 while (left && right) { if (left->val < right->val) { cur->next = left; left = left->next; } else { cur->next = right; right = right->next; } cur = cur->next; } // 如果哪一段还有剩余,直接连到cur后面 if (left) cur->next = left; else if (right) cur->next = right; // dh是虚拟头部节点,需要返回其后继 return dh->next; } };
- 时间复杂度:
- 空间复杂度:. 如上图所示,算法的递归深度最多为,每一层递归调用中,没有使用额外数组,所以每层的空间复杂度为, 整体空间复杂度为.