leetcode
leetcode 101 ~ 150
对链表进行插入排序

对链表进行插入排序

难度:

标签:

题目描述

给定单个链表的头 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

代码结果

运行时间: 192 ms, 内存: 16.5 MB


/*
题目思路:
1. 将链表的节点值收集到一个列表中。
2. 使用 Java Stream 对列表进行排序。
3. 将排序后的值重新转换回链表。
*/
 
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
 
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}
 
public class Solution {
    public ListNode insertionSortList(ListNode head) {
        if (head == null || head.next == null) return head;
        List<Integer> list = new ArrayList<>();
        ListNode curr = head;
        while (curr != null) {
            list.add(curr.val);
            curr = curr.next;
        }
        list = list.stream().sorted().collect(Collectors.toList());
        ListNode dummy = new ListNode(0);
        curr = dummy;
        for (int val : list) {
            curr.next = new ListNode(val);
            curr = curr.next;
        }
        return dummy.next;
    }
}

解释

方法:

该题解使用插入排序的思想对链表进行排序。具体步骤如下: 1. 首先判断链表是否为空或者只有一个节点,如果是则直接返回。 2. 创建一个虚拟头节点dummy,指向原始链表的头节点。 3. 用last_sorted表示已排序部分的最后一个节点,初始为头节点。 4. 用cur表示当前需要插入的节点,初始为头节点的下一个节点。 5. 遍历cur节点,对于每个cur节点: - 如果cur节点的值大于last_sorted节点的值,说明cur应该插入到已排序部分的最后,将last_sorted后移一位。 - 否则,从dummy节点开始遍历已排序部分,找到第一个大于cur的节点pre,将cur插入到pre之前。 6. 重复步骤5直到cur为空。 7. 返回dummy.next作为排序后的链表头节点。

时间复杂度:

O(n^2)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在处理插入排序时需要使用虚拟头节点`dummy`,直接使用原链表的头节点是否可行?
使用虚拟头节点`dummy`的主要原因是为了简化插入到链表头部的操作。在插入排序过程中,如果新的节点需要插入到当前所有已排序节点之前,使用虚拟头节点可以避免额外的条件判断来处理头节点的变化。如果直接使用原链表的头节点,每次插入到头部时都需要更新头节点,这会增加代码的复杂度并可能导致错误。
🦆
在插入排序中,如何确保在将`cur`节点插入到新位置时,不会丢失链表中的其他节点?
在将`cur`节点插入到新位置前,先将`cur`节点从其当前位置断开,这是通过设置`last_sorted.next`指向`cur.next`来完成的。然后,将`cur`插入到新的位置,这是通过设置`cur.next`指向`pre.next`,再将`pre.next`指向`cur`来完成的。这样的操作确保了在操作过程中,所有节点都被正确地维持和连接,没有任何节点会丢失。
🦆
当`cur`的值小于`last_sorted`的值时,为什么需要从虚拟头节点`dummy`开始遍历以找到插入位置,而不是从`last_sorted`或其他节点开始?
当`cur`的值小于`last_sorted`的值时,这表示`cur`需要被插入到已排序部分的某个位置,并非仅仅是`last_sorted`之后。因为`last_sorted`代表已排序部分的末尾,所以从`last_sorted`开始向前寻找会增加复杂性,并且可能需要额外的指针来追踪。从虚拟头节点`dummy`开始遍历可以线性地遍历已排序部分,简化了逻辑并且保证能找到正确的插入位置。
🦆
在插入排序过程中,如果`cur`节点需要插入到`last_sorted`之前,为什么还需要将`cur`从原来的位置移除?
如果`cur`节点需要插入到`last_sorted`之前的某个位置,意味着`cur`节点原来的位置不再是其在排序链表中的正确位置。因此,需要将`cur`节点从原位置移除,即断开`last_sorted`与`cur`之间的链接,然后将`cur`插入到正确的位置。这样的操作是为了保持链表的顺序性和正确性。

相关问题

排序链表

给你链表的头结点 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) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

循环有序列表的插入

循环有序列表的插入