leetcode148. 排序链表

传送门

题目: 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
输入: 4->2->1->3 输出: 1->2->3->4

对一个链表进行排序,且时间复杂度要求为 O(nlogn) ,空间复杂度为常量。一看到 O(nlogn)的排序,首先应该想到归并排序和快速排序,但是通常我们使用这两种排序方法时都是针对数组的,现在是链表了。

归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来数组使用归并时,都是 O(N)的,需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并排序不需要额外的空间)。

利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,直到快的走到了末尾,然后慢的所在位置就是中间位置(注意1->2这种情况),这样就分成了两段。merge时,把两段头部节点值比较,用一个冗余节点head当作新链表头结点,cur初始值head,每一次比较两个链表的头,cur->next指向left和right链表头较小的节点,移动对应链表头和cur。

主要考察3个知识点,
知识点1:归并排序的整体思想
知识点2:找到一个链表的中间节点的方法
知识点3:合并两个已排好序的链表为一个新的有序链表

Java:
class Solution {
   ListNode sortList(ListNode head) { // 归并,返回排序好的链表头
    	if (head == null || head.next == null) return head;
    	ListNode halfListNode = findHalfNode(head);
    	ListNode rightListHead = halfListNode.next;//右半部分链表头
    	halfListNode.next = null;
    	// 不能先调用两个递归sortList之后,传给mergeList参数:head和rightHalfHead
    	// 因为上一次合并后的链表 head 和 rightHalfHead指向的节点不一定是头节点了
    	return mergeList(sortList(head), sortList(rightListHead));
    }
    
    // 合并并排序两个未排序链表
    ListNode mergeList(ListNode left, ListNode right) {
    	ListNode dummy = new ListNode(-1);
    	ListNode fail = dummy;
    	while (left != null && right != null) {
    		if (left.val < right.val) {
    			fail.next = left;
    			left = left.next;
    		} else {
    			fail.next = right;
    			right =right.next;
    		}
    		fail = fail.next;
    	}
    	if (left == null) fail.next = right;
    	if (right == null) fail.next = left;
    	return dummy.next;
    }
    
   // 快慢指针找中间节点
    ListNode findHalfNode(ListNode head) {
    	if (head == null || head.next == null) return head;
    	ListNode slow = head, fast = head;
    	while (fast.next != null && fast.next.next != null) {
    		slow = slow.next;
    		fast = fast.next.next;
    	}
    	return slow;
    }
}
C++:
	ListNode* sortList(ListNode* head) {
       return head == NULL ? head : sortListCore(head);
    }

	ListNode* sortListCore(ListNode* head) {
         //递归出口,说明此子链表只剩一个节点,
         //最后一次拆分链表完成,开始合并
         //不能是head==null 因为head链表只有一个元素的时候 
         //midnode一直是这个元素  rightNode 一直是null 
         //head = sortListCore(head);会死递归
        if (head->next == NULL) return head;

        ListNode *mid = findMid(head); // 返回链表中间节点
        ListNode *rightHead = mid->next; //分割的右链表头
        mid->next = NULL; // 左链表尾
        head = sortListCore(head); //左递归
        rightHead = sortListCore(rightHead);//右递归
        return mergeList(head, rightHead);//合并左右两个子链表
    }

    // 合并两个链表,用冗余节点
	ListNode *mergeList(ListNode *leftHead, ListNode *rightHead) {
        ListNode *dummyHead = new ListNode(-1);
        ListNode *cur = dummyHead, *listA = leftHead, *listB = rightHead;
        while (listA != NULL && listB != NULL) {
            if (listA->val < listB->val) { 
                cur->next = listA;
                listA = listA->next;
            } else {
                cur->next = listB;
                listB = listB->next;
            }
            cur = cur->next;  // cur每次都要前进
        }
        if (listA != NULL) cur->next = listA; // 处理剩下的
        else               cur->next = listB;
        ListNode *ans = dummyHead->next;
        delete dummyHead;
        return ans;
	}

    ListNode *findMid(ListNode *head) {//快慢指针找链表的中间节点
        ListNode *slow = head, *fast= head;
        //fast->next->next 防止1,2这种情况返回2
        while (fast->next != NULL && fast->next->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
一定注意:while 条件 要包含 fast->next->next != NULL 
否则 1->2 这种情况返回mid=2,令右边链表头(rightHead=mid->next)NULL

可能你会说,在sortListCore递归出口里加上head==NULL的条件,
让rightHead是空的时候返回,
但是这时候1->2这种情况left链表一直被分割成1->2,right链表一直是NULL,
一直得不到处理啊!死循环!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值