lc148链表排序——链表版归并排序

148. 排序链表 - 力扣(LeetCode)

法1:递归版归并排序

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        // 归并排序
        // 确实链表可以实现哈,不像快排可能说需要双指针往中间靠拢,
        // 归并直接从前往后所以不涉及到获取数组中间值的操作,
        if(head == null)
            return head;
        return mergeSort(head);
    }

    public ListNode mergeSort(ListNode head) {
        //仅1个元素了
        if(head.next == null)
            return head;

        //快慢指针,且拆成2半
        ListNode secondHead = getSecondHead(head);

        head = mergeSort(head);
        secondHead = mergeSort(secondHead);
        return merge(head, secondHead);
    }

    //合并两个有序链表
    public ListNode merge(ListNode head, ListNode secondHead) {
        // 哑结点技巧
        ListNode dummyHead = new ListNode();
        ListNode curNode = dummyHead;
        while(head != null && secondHead != null) {
            if(head.val <= secondHead.val) {
                curNode.next = head;
                head = head.next;
            } else {
                curNode.next = secondHead;
                secondHead = secondHead.next;
            }
            //艹,忘了更新curNode
            curNode = curNode.next;
        }
        //如果head先为空
        if(head == null)
            curNode.next = secondHead;
        else 
            curNode.next = head;
        
        return dummyHead.next;
    }


    //快慢指针获取另一半首节点,前面已经检验至少2个节点,并且要断开为2个链表
    public ListNode getSecondHead(ListNode head) {
        //初始化
        ListNode slow = head;
        ListNode fast = head.next;

        while(fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        //注意必须要断成2节
        head = slow.next;
        slow.next = null;
        return head;
    }
}

空间复杂度为递归栈空间,logn,因为链表特性不像数组版标准归并排序,还需要O(n)的额外数组来合并2个旧数组。

法2:迭代版归并排序

递归是要自顶向下进行分治,一分二,二分四......它是自顶往下调用的,虽然计算仍然是从下往上计算得出然后返回的。

但可以直接用自底向上的思想,直接先从底部计算,再往上“累积”,避开了自顶向下调用的过程。但实现就要麻烦很多。

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

        //获取长度
        int n = getLen(head);
        
        ListNode dummyHead = new ListNode(0, head);
        ListNode curNode;
        ListNode preLinkLast;
        ListNode[] heads;
        ListNode head1, head2;
        //step是每次合并的两链表的长度
        for(int step = 1; step < n; step *= 2) {
            curNode = dummyHead.next;
            preLinkLast = dummyHead;
            //2个2个的合并
            while(curNode != null) {
                head1 = curNode;
                // splitList方法,是获取head开头的长step的链表,
                // 并将其与后面断开,且返回断开后的下一个链表头
                head2 = splitList(head1, step);
                //下一轮迭代的head
                curNode = splitList(head2, step);

                //合并2个有序链表,并返回合并的链表头和尾
                heads = mergeTwoLists(head1, head2);
                preLinkLast.next = heads[0];
                preLinkLast = heads[1];
            }
        }
        return dummyHead.next;
    }

    //返回链表总长度
    public int getLen(ListNode head) {
        int cnt = 0;
        while(head != null) {
            cnt++;
            head  = head.next;
        }
        return cnt;
    }

    //或取head开头长step的链表,并将其与后面断开
    // 并返回断开后的head
    // 若长度不到step,返回null
    public ListNode splitList(ListNode head, int step) {
        int cnt = 1;
        while(head != null && cnt < step) {
            head = head.next;
            cnt++;
        }

        if(head == null)
            return null;
        ListNode nextHead = head.next;
        head.next = null;
        return nextHead;
    }

    // 合并2个有序链表,并返回新的合并后的head和tail
    public ListNode[] mergeTwoLists(ListNode head1, ListNode head2){
        ListNode dummyHead = new ListNode();
        ListNode curNode = dummyHead;

        while(head1 != null && head2 != null) {
            if(head1.val <= head2.val) {
                curNode.next = head1;
                head1 = head1.next;
            }
            else {
                curNode.next = head2;
                head2 = head2.next;
            }
            curNode = curNode.next;
        }
        
        if(head1 == null)
            curNode.next = head2;
        else 
            curNode.next = head1;

        while(curNode.next != null) {
            curNode = curNode.next;
        }

        return new ListNode[]{dummyHead.next, curNode};
    }
}

TIP:当方法需要返回2个节点时,不一定就用内部类,用数组ListNode[]也行,还能简化些和更清晰些。

TIP:当需要很多局部变量时,可以先循环内定义把逻辑写完,后面再优化提出到循环外定义,直接一开始就把所有变量定义好比较难想,而且容易昏乱

TIP:这里不要采用快慢指针求双链表了,而是用splitList方法,取之后step长的链表并断开,用快慢指针逻辑会变复杂,又要返回断开后2个链表的头,还要返回下一轮迭代链表的头,还要考虑链表长度在0-step,step-2step, >2step几种情况,太烦了。

还是递归好,迭代写起来还挺麻烦的,而且logn空间复杂度其实非常低,n = 1百万时 logn才20,1亿时logn也就26.57左右,其实相比之下还是非常小的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值