1)题目
对乱序的链表进行排序,要求空间复杂度为常数。(LeetCode 148 中等)
输入: 4->2->1->3
输出: 1->2->3->4
输入: -1->5->3->4->0
输出: -1->0->3->4->5
2)思路
对于这题我有两种解法,一种是时间复杂度O(n2)的暴力解法,一种是时间复杂度O(nlogn)的基于归并思想的解法。
对于暴力解法,我们会将整个链表分为“有序段”和“无序段”两段。设立一个虚拟的头指针q,其next域指向“有序段”的第一个节点;再设定一个指针p直接指向无序链表的第一个节点。其排序思路是将无序链表中的第一个节点p与有序链表中的所有节点挨个比较(通过不停移动q指针实现),找到合适的位置插入p即可。在刚开始的时候有序链表中只有原链表中的第一个节点,无序链表则有剩下的全部节点。由此可以看出总的时间复杂度为O(n2)。
对于归并解法,归并的思想是不停的寻找链表的中间位置,并且通过递归将整个链表拆分成多个长度为1或者2的小链表,在基础上按大小顺序合并成新的链表。这个思想在数组的排序中很好理解,但是在链表的排序中需要注意a.将每段最后一个元素的next域指向空;b.构造一个next域指向合并后有序链表的第一个节点的虚拟指针;由此可见总的时间复杂度为O(nlogn)。
2)代码
// 1.复杂度O(n^2)的暴力解法
public static ListNode sortList1(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode p = head;
ListNode r = p.next;
p.next = null;
// 头结点(非元素节点)其next阈永远指向已排序链表中的第一个节点
ListNode record = new ListNode(-1); //该节点的next域一直记录有序链表的头结点
record.next = head; //刚开始的时候,有序链表中只有原链表中的第一个元素。
p = r; //p指向无序链表中的第一个节点
while (p != null) {
r = p.next;
ListNode q = record; // 拿到有序链表中的第头结点(非元素节点)
//寻找p节点的插入位置
while (q != null && q.next != null && q.next.val < p.val)
q = q.next;
p.next = q.next;
q.next = p;
p = r;
}
return record.next;
}
// 2.复杂度O(nlogn)的归并解法
public static ListNode sortList2(ListNode head) {
if(head == null || head.next == null)
return head;
//将链表一分为二
ListNode mid = getMidNode(head);
ListNode begin = mid.next;
mid.next = null;
ListNode headA = sortList2(head);
ListNode headB = sortList2(begin);
return merge(headA, headB);
}
// 首先找到链表中的中间节点
static ListNode getMidNode(ListNode head) {
// 基于快慢指针的思想找中间节点
ListNode fast = head;
ListNode slow = head;
// 当“快”指针为空时,“慢”指针就指向中间节点
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
// 基于归并排序的思想,这里传入的链表A与链表B都是有序的
static ListNode merge(ListNode headA, ListNode headB) {
ListNode head = new ListNode(-1);// 这里是新new出来的头结点,指向有序链表的第一个元素节点
ListNode record = head;
while (headA != null && headB != null) {
if(headA.val < headB.val){
head.next = headA;
headA = headA.next;
}else{
head.next = headB;
headB = headB.next;
}
head = head.next;
}
head.next = headA == null ? headB : headA;
return record.next;
}