这是一道中等题,如果不考虑题目的进阶要求,可能就是一道简单题吧,因为你可以在直接使用一个O(n)的空间进行解决的话,是十分简单的,但是如果严格按照题目进阶要求,我个人认为这还是算一道困难题。因此折中就为中等题。
你可以在
O(n log n)时间复杂度和常数级空间复杂度下,对链表进行排序吗?
因为我曾经看过算法书,里面就有归并排序,不过这是关于数组的问题,我也是记得是有两种优化方法:
思路一 :从上到下的分治方法,也就是递归方法,类似于二分查找在数组中,在链表中也很容易想到使用快慢指针定位中点,分完后,又利用一个O(n)的空间的数组进行归并,链表也就不需要多余的空间,只需要改变指针即可。乍一看,好像符合题目要求,当时我也是这么觉得的,很遗憾该题解不合要求,时间复杂度合格,但是空间复杂度因为递归就会产生logn的空间复杂度。
代码
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode res = DFS(head);
return res;
}
// 由上至下的递归归并排序,时间复杂度合格,空间复杂度不为常数级别。为递归调用的栈空间O(logn)
private ListNode DFS(ListNode node){
if (node.next == null) return node;
ListNode f = node;
ListNode s = node;
while (f.next != null && f.next.next != null){
s = s.next;
f = f.next.next;
}
f = s.next;
s.next = null;
ListNode node1 = DFS(node);
ListNode node2 = DFS(f);
s = merge(node1,node2);
return s;
}
private ListNode merge(ListNode node1, ListNode node2){
ListNode res = new ListNode();
ListNode pointer = res;
while (node1 != null || node2 != null){
if (node1 == null){
pointer.next = node2;
break;
}
else if (node2 == null) {
pointer.next = node1;
break;
}
else if (node1.val > node2.val) {
pointer.next = node2;
node2 = node2.next;
}else{
pointer.next = node1;
node1 = node1.next;
}
pointer = pointer.next;
}
return res.next;
思路二 后来我看题解很多人说到该问题,于是我就看了第二种方法,也就是迭代法,不过说实话,当初我看书的时候,就没有看太懂,这也是一个分治方法,这两种方法同数组几乎一样,但是相对于链表来说,第二种方法更好,因为其只需要o(1)也就是常数级别的空间,具体思路就是从下到上的分治,首先分为 1 进行合并,其次就是2、4、8、16、、、直到大于链表的空间,听起来思路还是很简单,我写了好久,真的麻了。。。。。。
代码
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode dummy = new ListNode(-1,head);
int length = 0;
ListNode node = head;
while (node != null){
node = node.next;
length++;
}
ListNode pre;
ListNode cur;
for (int subLength = 1; subLength < length; subLength *= 2){
pre = dummy;
cur = dummy.next;
while (cur != null){
ListNode head1 = spilt(cur, subLength);
ListNode nextHead = spilt(head1,subLength);
ListNode listNode = merge(cur, head1);
cur = nextHead;
pre.next = listNode;
while (listNode.next != null) listNode = listNode.next;
pre = listNode;
}
}
return dummy.next;
}
private ListNode merge(ListNode head1, ListNode head2){
ListNode dummy = new ListNode(-1);
ListNode pointer = dummy;
while (head1 != null || head2 != null){
if (head1 == null) {
pointer.next = head2;
break;
}
if (head2 == null) {
pointer.next = head1;
break;
}
if (head1.val > head2.val){
pointer.next = head2;
head2 = head2.next;
}else {
pointer.next = head1;
head1 = head1.next;
}
pointer = pointer.next;
}
return dummy.next;
}
private ListNode spilt(ListNode node, int subLength){
if (node == null) return null;
ListNode nextHead;
for (int i = 1; i < subLength && node.next != null; i++) {
node = node.next;
}
nextHead = node.next;
node.next = null;
return nextHead;
}
注意 两者的merge方法不一样,还是得多学习官方的解法也就是多封装复用的地方。 相对于来说第一种方法更易于理解,更简单。但是如果面试时遇到这题,不用想面试官需要你写的是第二种方法,就不要想那些投机取巧的方法了。真的难啊。。。。。