LeetCode 对链表进行插入排序/排序链表(归并)[排序]

1,074 阅读2分钟

这是我参与更文挑战的第 8 天,活动详情查看: 更文挑战

对链表进行插入排序(147)

题目描述

从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。

插入排序算法:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

示例 1:

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

示例 2:

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

思路分析

对链表进行插入排序,时间复杂度是O(n2){O(n^2)},空间复杂度是O(1){O(1)}。首先有一个已排序好的链表和未排序好的链表,每次将未排序好的链表中添加一个到已排序好的链表上,这个新插入的链表节点的判断条件是什么呢,就是这个链表节点.next 大于已排序好的节点。

然后在将这个链表节点插入指定位置,同时继续指向下一个节点。

代码展示

解法一:时间复杂度是O(n2){O(n^2)},空间复杂度是O(1){O(1)}

public ListNode insertionSortList(ListNode head) {  //2-3-5-  7
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            return head;
        }

        ListNode newNode = new ListNode(Integer.MIN_VALUE);
        ListNode p = head;
        while (p != null){

            ListNode temp = p.next;
            ListNode q = newNode;  //新的链表节点
            while (q.next != null && q.next.val <= p.val){ //循环终止条件    1-2-4   3     q = 2
                q = q.next;
            }
            //插入p节点
            p.next = q.next; //3.next = [2.next = 4]
            q.next = p;  //2.next  = 3

            p = temp;  //p继续指向下一个节点
        }
        return newNode.next;
    }

排序链表(148)

题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

进阶

  • 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

思路分析

熟悉排序算法的同学都知道,时间复杂度是O(nlogn){O(nlogn)}的排序算法包括,归并排序,堆排序和快速排序,堆排序和快速排序的最差时间复杂度是O(n2){O(n^2)},其中最适合链表的排序算法是归并排序,但是归并排序基于分治算法,最容易想到的方式是自顶向下的递归实现,这个时候递归调用栈导致的空间复杂度是O(logn){O(logn)},如果要达到O(1){O(1)}的时间复杂度,则需要使用自底向上的实现方式。

代码展示

解法一:自顶向下归并排序,时间复杂度是O(nlogn){O(nlogn)},空间复杂度是O(logn){O(logn)}

public ListNode sortList(ListNode head) {  //合并两个有序链表   归并排序链表  4,3,2,1
        if (head == null){
            return null;
        }
        if (head.next == null){
            return head;
        }

        //这里需要注意的是,奇数查找的是中间节点,偶数的话比如4,3,2,1     midNode = 3 是停留在这个节点,这样偶数是均分的,所以需要注意查找中间节点的写法
        ListNode midNode = findMiddleNode(head); //4,3
        ListNode secNode = midNode.next;         //2,1
        midNode.next = null;

        ListNode first = sortList(head);
        ListNode sec = sortList(secNode);

        ListNode newNode = mergetTwoSortedListNode(first,sec);
        return newNode;

    }

    private ListNode mergetTwoSortedListNode(ListNode one,ListNode two){
        if (one == null){
            return two;
        }
        if (two == null){
            return one;
        }
        ListNode p = new ListNode();

        ListNode newNode = p;

        while (one != null && two != null){
            if (one.val >= two.val){
                p.next = two;
                two = two.next;
            } else {
                p.next = one;
                one = one.next;
            }
            p = p.next;
        }
        if (one == null){
            p.next = two;
        }
        if (two == null){
            p.next = one;
        }

        return newNode.next;

    }

    private ListNode findMiddleNode(ListNode head){
        ListNode quick = head;
        ListNode slow = head;
        while (quick.next != null && quick.next.next != null){
            quick = quick.next.next;
            slow = slow.next;
        }
        return slow;
    }

解法二:自底向上归并排序,时间复杂度是O(nlogn){O(nlogn)},空间复杂度是O(1){O(1)}

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) {
            return head;
        }
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        ListNode dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode prev = dummyHead, curr = dummyHead.next;
            while (curr != null) {
                ListNode head1 = curr;
                for (int i = 1; i < subLength && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode head2 = curr.next;
                curr.next = null;
                curr = head2;
                for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {
                    curr = curr.next;
                }
                ListNode next = null;
                if (curr != null) {
                    next = curr.next;
                    curr.next = null;
                }
                ListNode merged = merge(head1, head2);
                prev.next = merged;
                while (prev.next != null) {
                    prev = prev.next;
                }
                curr = next;
            }
        }
        return dummyHead.next;
    }

    public ListNode merge(ListNode head1, ListNode head2) {
        ListNode dummyHead = new ListNode(0);
        ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
        while (temp1 != null && temp2 != null) {
            if (temp1.val <= temp2.val) {
                temp.next = temp1;
                temp1 = temp1.next;
            } else {
                temp.next = temp2;
                temp2 = temp2.next;
            }
            temp = temp.next;
        }
        if (temp1 != null) {
            temp.next = temp1;
        } else if (temp2 != null) {
            temp.next = temp2;
        }
        return dummyHead.next;
    }
}

总结

对于常用的排序算法,数组的实现我们应该掌握,也应该掌握插入排序和归并排序在链表上的实现。