题目
标题和出处
标题:对链表进行插入排序
难度
5 级
题目描述
要求
给定链表的头结点 head \texttt{head} head,使用插入排序对链表进行升序排序,并返回排序后的链表的头结点。
插入排序算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个动图示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
示例
示例 1:
输入:
head
=
[4,2,1,3]
\texttt{head = [4,2,1,3]}
head = [4,2,1,3]
输出:
[1,2,3,4]
\texttt{[1,2,3,4]}
[1,2,3,4]
示例 2:
输入:
head
=
[-1,5,3,4,0]
\texttt{head = [-1,5,3,4,0]}
head = [-1,5,3,4,0]
输出:
[-1,0,3,4,5]
\texttt{[-1,0,3,4,5]}
[-1,0,3,4,5]
数据范围
- 链表中结点的数目范围是 [1, 5000] \texttt{[1, 5000]} [1, 5000]
- -5000 ≤ Node.val ≤ 5000 \texttt{-5000} \le \texttt{Node.val} \le \texttt{5000} -5000≤Node.val≤5000
解法
思路和算法
对链表插入排序,需要维护一个已排序的子链表,排序过程中将未排序的结点插入到已排序的子链表中的合适位置。由于待插入的结点值可能小于已排序的子链表中的最小结点值,为了方便插入操作,需要创建哑结点,将链表的头结点 head \textit{head} head 作为哑结点的后一个结点,则每次插入结点的位置一定在哑结点之后。
用 end \textit{end} end 表示已排序的子链表的最后一个结点。初始时 end = head \textit{end} = \textit{head} end=head,表示已排序的子链表只有头结点。当 end \textit{end} end 变成链表的最后一个结点时,所有结点都在已排序的子链表中,整个链表的排序结束。
当 end \textit{end} end 不是链表的最后一个结点时,链表中有尚未排序的结点,下一个待排序的结点是 end . next \textit{end}.\textit{next} end.next。比较 end \textit{end} end 的结点值与 end . next \textit{end}.\textit{next} end.next 的结点值,执行以下操作。
-
如果 end \textit{end} end 的结点值小于等于 end . next \textit{end}.\textit{next} end.next 的结点值,则 end . next \textit{end}.\textit{next} end.next 应该插入到 end \textit{end} end 的后面,此时这两个结点之间的相对顺序已经正确,因此将 end \textit{end} end 移动到 end . next \textit{end}.\textit{next} end.next。
-
如果 end \textit{end} end 的结点值大于 end . next \textit{end}.\textit{next} end.next 的结点值,则 end . next \textit{end}.\textit{next} end.next 应该插入到已排序的子链表中。用 next \textit{next} next 表示 end . next \textit{end}.\textit{next} end.next,从哑结点开始遍历链表,找到值小于等于 next \textit{next} next 的结点值的最后一个结点 prev \textit{prev} prev,满足 prev \textit{prev} prev 的结点值小于等于 next \textit{next} next 的结点值且 prev . next \textit{prev}.\textit{next} prev.next 的结点值大于 next \textit{next} next 的结点值,将 next \textit{next} next 插入到 prev \textit{prev} prev 的后面。具体插入操作如下。
- 令 end . next \textit{end}.\textit{next} end.next 指向 next . next \textit{next}.\textit{next} next.next。
- 令 next . next \textit{next}.\textit{next} next.next 指向 prev . next \textit{prev}.\textit{next} prev.next。
- 令 prev . next \textit{prev}.\textit{next} prev.next 指向 next \textit{next} next。
当整个链表的排序结束之后,返回哑结点的后一个结点,即排序后的链表的头结点。
假设待插入结点的结点值是 val \textit{val} val。由于插入结点时都是寻找值小于等于 val \textit{val} val 的最后一个结点,如果已排序的子链表中存在值等于 val \textit{val} val 的结点,则待插入结点的插入位置一定在值等于 val \textit{val} val 的结点右侧,因此链表的插入排序算法是稳定的。
代码
class Solution {
public ListNode insertionSortList(ListNode head) {
ListNode dummyHead = new ListNode(0, head);
ListNode end = head;
while (end.next != null) {
if (end.val <= end.next.val) {
end = end.next;
} else {
ListNode prev = dummyHead, next = end.next;
while (prev.next.val <= next.val) {
prev = prev.next;
}
end.next = next.next;
next.next = prev.next;
prev.next = next;
}
}
return dummyHead.next;
}
}
复杂度分析
-
时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是链表的长度。插入排序需要遍历链表一次,对于每个结点最多需要 O ( n ) O(n) O(n) 的时间遍历链表寻找插入位置并插入结点,因此链表插入排序的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
-
空间复杂度: O ( 1 ) O(1) O(1)。