在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。java
示例 1:node
输入: 4->2->1->3 输出: 1->2->3->4
示例 2:git
输入: -1->5->3->4->0 输出: -1->0->3->4->5
原题url:https://leetcode-cn.com/probl...github
题目很明确,排序,对于时间复杂度和空间复杂度有要求,针对O(n log n)
,让我想到了归并排序
和快速排序
,接下来咱们各自来看看。segmentfault
对了,这里先统一放一下节点类,单向链表中的节点,存储当前节点的值和后一个节点的引用。测试
Definition for singly-linked list. public class ListNode { int val; ListNode next; ListNode(int x) { val = x; } }
归并排序,说白了,就是先分解到最小单元,而后逐个进行合并并排序,这样在合并的时候,其实两个链表自己就是有序的,那么当有一个所有取完后,另外一个能够直接拼接在最后面。优化
让咱们看一下代码:url
public class Solution { public ListNode sortList(ListNode head) { // 归并排序 if (head == null || head.next == null) { return head; } // 先分隔,利用快慢指针分隔。 // 快指针先走,由于只有当空节点或1个节点才是终止条件,2个节点的时候,若是不让快指针先走,而是也指向head,那么2个节点永远不会被分隔,会陷入死循环 ListNode fast = head.next.next; ListNode slow = head; while (true) { if (fast == null || fast.next == null) { break; } fast = fast.next.next; slow = slow.next; } // 后半部分的开头 ListNode second = slow.next; second = sortList(second); // 前半部分的开头 slow.next = null; ListNode first = head; first = sortList(first); // 合并 ListNode result = new ListNode(0); head = result; while (first != null && second != null) { if (first.val < second.val) { result.next = first; first = first.next; } else { result.next = second; second = second.next; } result = result.next; } if (first != null) { result.next = first; } else { result.next = second; } return head.next; } }
提交OK,执行用时:5 ms
,内存消耗:39.6 MB
,执行用时只打败了59.07%
的 java 提交记录,应该还有优化的空间。spa
针对上面的代码,在分隔的时候,设置fast = head.next.next
,这是由于咱们设置的递归终止条件是针对null
或者单个节点的。其实当只剩下两个节点的时候,就能够进行排序了,这样应该能够节省近一半的时间,固然了,从时间复杂度上来讲并无改变。指针
咱们看一下代码:
public class Solution { public ListNode sortList(ListNode head) { // 归并排序 if (head == null || head.next == null) { return head; } // 说明只有两个节点 if (head.next.next == null) { ListNode second = head.next; if (head.val > second.val) { return head; } else { second.next = head; head.next = null; return second; } } // 先分隔,利用快慢指针分隔。 ListNode fast = head; ListNode slow = head; while (true) { if (fast == null || fast.next == null) { break; } fast = fast.next.next; slow = slow.next; } // 后半部分的开头 ListNode second = slow.next; second = sortList(second); // 前半部分的开头 slow.next = null; ListNode first = head; first = sortList(first); // 合并 ListNode result = new ListNode(0); head = result; while (first != null && second != null) { if (first.val < second.val) { result.next = first; first = first.next; } else { result.next = second; second = second.next; } result = result.next; } if (first != null) { result.next = first; } else { result.next = second; } return head.next; } }
执行用时,有的时候是4 ms
,有的时候是3 ms
,看来归并排序这条路差很少就是这样了。
快速排序的思想就是选择一个标准值,将比它大的和比它的小的,作交换。针对链表这种结构,就是将比它大的放在一个链表中,比它小的放在一个链表中,和它同样大的,放在另外一个链表中。而后针对小的和大的链表,继续排序。最终将三个链表按照小、相等、大进行链接。
接下来让咱们看看代码:
class Solution { public ListNode sortList(ListNode head) { // 利用快排 // 单个节点是终止节点 if (head == null || head.next == null) { return head; } // 比标准值小的节点 ListNode lowHead = new ListNode(0); ListNode low = lowHead; // 和标准值同样的节点 ListNode midHead = new ListNode(0); ListNode mid = midHead; // 比标准值大的节点 ListNode highHead = new ListNode(0); ListNode high = highHead; // 标准值 int val = head.val; ListNode node = head; // 遍历 while (node != null) { // 比标准值大的节点 if (node.val > val) { high.next = node; high = high.next; } // 比标准值小的节点 else if (node.val < val) { low.next = node; low = low.next; } // 和标准值同样的节点 else { mid.next = node; mid = mid.next; } node = node.next; } // 终止,避免形成环 low.next = null; high.next = null; lowHead.next = sortList(lowHead.next); highHead.next = sortList(highHead.next); // 找出小节点链表的末尾 low = lowHead; while (low.next != null) { low = low.next; } // 拼接 low.next = midHead.next; mid.next = highHead.next; return lowHead.next; } }
提交OK,执行用时:2 ms
,内存消耗:40.01 MB
。
和归并排序相比,时间更短,至于缘由,我确实是没有想明白,由于都须要比较,而后从新构造新链表。我猜想是测试数据离散程度更高,这样归并排序的话,并无充分利用其特性:
当两个链表合并时,若是一个链表已经所有结束,另外一个链表剩余的部分能够直接拼接。
以上就是这道题目个人解答过程了,不知道你们是否理解了。针对它的时间复杂度要求,利用归并排序或者快速排序解决。
有兴趣的话能够访问个人博客或者关注个人公众号、头条号,说不定会有意外的惊喜。
公众号:健程之道