力扣148. 排序链表(归并排序)

力扣148. 排序链表

在 O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4

示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

分析

题目要求时间复杂度是O(nlogn),就需要二分,既然是二分,就确定了归并排序。

空间复杂度要求O(1),就不能使用递归,并且不能创建额外的空间来保存临时数组。

题目是对链表进行排序,链表可以通过指针指向新节点进行排序,所以实现了O(1)的空间复杂度。

既然不能用递归,就根据递归的思想,从底向上进行归并。

第一轮循环,我们处理没两个节点,将节点两两排序。

第二轮循环,现在节点是两两有序,我们就每四个进行排序。

直到我们的排序规模超过了链表长度,就说明链表全部有序。

原链表:      4   ->   2   ||->   1   ->   3
第一轮排序后:   2 -> 4   ||-> 1 -> 3
第二轮排序:   1 -> 2 -> 3 -> 4

原链表:      -1  ->  5  ||->  3  ->  4||  ->  0
第一轮排序后:  -1 -> 5  || -> 3 -> 4  || -> 0
第二轮排序后: -1 -> 3 -> 4 -> 5  || -> 0
第三轮排序后: -1 -> 0 -> 3 -> 4 -> 5

注意:

  • 寻找每两个待合并的链表时,要时刻注意越界问题。
  • 链表1可能长度不够排序规模,链表2也可能不够排序规模。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }

        //统计链表长度
        int len = 0;
        ListNode tmp = head;
        while(tmp != null){
            len++;
            tmp = tmp.next;
        }
        //System.out.println("链表长度为:"+len);

        //创建虚假头节点
        ListNode dumHead = new ListNode(0);
        dumHead.next = head;

        //sortLen是每次合并的链表的规模,
        //从每次合并两个单节点开始,第二次合并两个长度为2的节点,第三次合并两个长度为8的节点,直到合并完毕。
        int sortLen = 1;
        while(sortLen<len){
            ListNode pre = dumHead;
            ListNode h = dumHead.next;
            //找待合并的两个链表的头结h1和h2
            while(h!=null){
                //h1就是当前头结点
                ListNode h1 = h;

                //寻找h2
                int i = sortLen;
                while(i>0){
                    if(h == null) break;
                    h = h.next;
                    i--;
                    //System.out.println(i);
                }

                //如果i>0,说明h1这个长度还没有待合并的长度长,所以肯定h2不存在。
                if(i>0) break;

                ListNode h2 = h;

                //将h移动到h2这一段链表后面
                i = sortLen;
                while(i>0){
                    if(h == null) break;
                    h = h.next;
                    i--;
                }

                // 求出两个链表的长度

                //h1完整地走完了一个sortLen,所以长度就是sortLen
                int c1 = sortLen;
                //h2不一定走完了sortLen,所以长度是sortLen-i
                int c2 = sortLen - i;
 
                //合并h1和h2
                /*
                4->2
                h1 h2
                */
                
                //先合并前面长度相同的一部分
                //pre节点在h1之前,保存到现在就是为了合并时能将h1和h2合并后的链表和前面的连接在一起。
                while(c1 > 0 && c2 > 0) {
                    if(h1.val < h2.val) {
                        pre.next = h1;
                        h1 = h1.next;
                        c1--;
                    }else {
                        pre.next = h2;
                        h2 = h2.next;
                        c2--;
                    }
                    //既然排序了一个数字,那么pre就可以往后挪了。
                    pre = pre.next;
                }
                //如果h1或者h2还没排序完,接着排序尾巴
                //将pre指向尾巴
                pre.next = c1 > 0 ? h1 : h2;

                //将pre指向尾巴的末尾。
                while(c1 > 0 || c2 > 0) {
                    pre = pre.next;
                    c1--;
                    c2--;
                }
                //合并完成,将pre指向下一个准备排序的头节点,直到这一趟走完,h指向了null。
                pre.next = h;
            }
            //一轮排序结束,合并规模翻倍。
            sortLen*=2;
            //合并规模超过了总长度,结束。
        }
        return dumHead.next;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值