leetcode
leetcode 101 ~ 150
排序链表

排序链表

难度:

标签:

题目描述

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

 

示例 1:

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

示例 2:

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

示例 3:

输入:head = []
输出:[]

 

提示:

  • 链表中节点的数目在范围 [0, 5 * 104] 内
  • -105 <= Node.val <= 105

 

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

代码结果

运行时间: 520 ms, 内存: 29.9 MB


/*
 * 思路:
 * 1. 将链表转化为流(Stream),然后排序。
 * 2. 由于Java的Stream API不支持对链表进行排序,我们将链表转换为ArrayList进行处理。
 * 3. 排序完成后,再将ArrayList转回链表。
 */
 
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
 
public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}
 
public class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) return null;
        // 将链表转化为List
        List<ListNode> nodeList = new ArrayList<>();
        while (head != null) {
            nodeList.add(head);
            head = head.next;
        }
        // 使用Stream排序
        nodeList = nodeList.stream()
                .sorted((n1, n2) -> Integer.compare(n1.val, n2.val))
                .collect(Collectors.toList());
        // 重新构造链表
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (ListNode node : nodeList) {
            current.next = node;
            current = current.next;
        }
        current.next = null; // 确保最后一个节点的next为null
        return dummy.next;
    }
}

解释

方法:

该题解使用了自底向上的归并排序算法。首先通过 dummy 节点简化边界情况处理。然后通过 get_length 函数获取链表长度。在归并排序的每一轮中,将链表拆分成若干个长度为 i 的子链表(最后一个子链表长度可能小于 i),并对每对相邻的子链表进行合并。通过 cut 函数将链表切成两半,再通过 merge 函数合并有序链表。不断将 i 乘以 2,对越来越长的有序子链表进行归并,直到整个链表有序。

时间复杂度:

O(n * log n)

空间复杂度:

O(1)

代码细节讲解

🦆
在使用自底向上的归并排序时,为什么选择这种方法而不是递归的自顶向下归并排序?
自底向上的归并排序在链表排序中被选择主要是因为它不依赖于递归调用,从而避免了额外的栈空间消耗,这对于处理大长度链表时更为高效。此外,自底向上的归并排序通过迭代方式逐步扩展子链表的长度,可以更自然地适应链表结构,而不需要像自顶向下那样进行多次链表的切分操作,这些切分操作本身就增加了额外的时间开销。
🦆
函数 `cut` 在遇到子链表长度不足指定长度时是如何处理的,它是否直接返回了剩余部分作为一个子链表?
函数 `cut` 设计用于将链表切分为指定长度的两部分。当链表长度达不到指定的长度时,`cut` 函数会将整个链表作为左侧链表,而右侧链表则为空。具体而言,当遍历完指定长度后,若后续没有更多节点,则返回的 `new_head` 为 `None`,表示右侧子链表不存在,左侧链表包含了所有剩余节点。
🦆
对于 `merge` 函数,如何确保合并过程中链表的稳定性,即不改变相同元素的原始顺序?
在 `merge` 函数中,确保链表稳定性的关键是在比较两个链表节点的值时,当两个节点的值相等时,优先链接左侧链表的节点。这样做保证了在输入链表中具有相同值的节点在输出链表中保持原有的顺序,从而维持了算法的稳定性。
🦆
在每一轮归并结束时,如何处理尾部可能悬空的子链表?
在每一轮归并结束时,可能存在未能与其他子链表合并的尾部子链表。这种情况下,该尾部子链表已经是有序的,可以直接将其连接到当前归并链表的末尾。`tail` 指针用于跟踪当前归并链表的末端,确保无论是完整合并还是简单连接尾部子链表,都能正确维护链表的整体连贯性。

相关问题

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

 

示例 1:

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

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

 

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1l2 均按 非递减顺序 排列

颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 12 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

 

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

 

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

 

进阶:

  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

对链表进行插入排序

给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。

插入排序 算法的步骤:

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

下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

对链表进行插入排序。

 

示例 1:

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

示例 2:

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

 

提示:

  • 列表中的节点数在 [1, 5000]范围内
  • -5000 <= Node.val <= 5000