148. 排序链表

259 阅读3分钟

题目介绍

力扣148题:leetcode-cn.com/problems/so…

image.png

image.png

方法一:优先队列

该题目的解题思路跟[23. 合并K个升序链表]类似,可是使用优先队列,把链表节点放入一个最小堆,就可以每次获得链表中的的最小节点,依次使用哑结点连接起来即可,代码如下:

/**
 * 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) {
        //哨兵节点,指向新链表头节点的上一个节点
        ListNode preHead = new ListNode(-1);
        ListNode pre = preHead;

        //创建优先队列,使用小顶堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>((a,b) -> {
            return a.val - b.val;
        });
        ListNode temp = head;
        while(temp != null) {
            //加入优先队列
            pq.add(temp);
            temp = temp.next;
        }
        
        while(!pq.isEmpty()) {
            //获取小顶堆最小值,即堆顶元素
            ListNode node = pq.poll();
            pre.next = node;
            //防止循环链表
            if(pq.size() == 0) {
                node.next = null;
            }
            //pre指针不断前进
            pre = pre.next;
        }
        return preHead.next;
    }
}

方法二:自顶向下归并排序

归并排序其实就是二叉树后续遍历,我们常见的对数组的归并排序代码如下:

public class MergetSort {

    public static void main(String[] args) {
        int arr[] = {8, 4, 7, 5};
        int temp[] = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, temp);
        System.out.println("归并排序后= " + Arrays.toString(arr));

    }


    //先分再治
    //分
    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            //左递归
            mergeSort(arr, left, mid, temp);
            //右递归
            mergeSort(arr, mid + 1, right, temp);

            //排序
            merge(arr, left, mid, right, temp);
        }
    }


    //合并
    //治
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int t = 0;

        //将左边的数与右边的数进行比较
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else {
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }

        while (i <= mid) {
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }

        while (j <= right) {
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }

        //将temp中的数据拷贝到arr中
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }

    }
}

对链表自顶向下归并排序的过程如下。

找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。

对两个子链表分别排序。将两个排序后的子链表合并,得到完整的排序后的链表。可以使用「21. 合并两个有序链表」的做法,将两个有序的子链表进行合并。

上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 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) {
        return sortList(head, null);
    }

    public ListNode sortList(ListNode head, ListNode tail) {
        //为空或者只有一个元素,直接返回
        if (head == null || head.next == null) {
            return head;
        }
        //只有两个元素 类似于 4->2 直接断开,返回4,2,然后进行merge排序合并
        if (head.next == tail) {
            head.next = null;
            return head;
        }
        ListNode slow = head, fast = head;
        while (fast != tail) {
            slow = slow.next;
            fast = fast.next;
            if (fast != tail) {
                fast = fast.next;
            }
        }
        ListNode mid = slow;
        ListNode list1 = sortList(head, mid);
        ListNode list2 = sortList(mid, tail);
        ListNode sorted = merge(list1, list2);
        return sorted;
    }

    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;
    }
}

复杂度分析

  • 时间复杂度: O(nlogn),其中 n 是链表的长度。

  • 空间复杂度: O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。