LeetCode Java刷题笔记—148. 排序链表
Posted 刘Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode Java刷题笔记—148. 排序链表相关的知识,希望对你有一定的参考价值。
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
由于需要O(nlogn) 时间复杂度,那么肯定就是归并排序、快速排序和堆排序。
实际上链表排序大部分都是用归并排序,它是一种稳定的排序。所谓归并排序,它采用了分治思想(Divide and Conquer)。分(divide)阶段将问题分成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"合并"在一起,即分而治之。
归并算法的原理是:如果初始序列含有n个记录,先将总记录拆分成相同长度两个子序列,然后再对两个子序列继续拆分(利用了递归),最终拆分成n个有序的子序列,每个子序列的长度为1;然后两两归并,得到|n/2|(|x|表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,如此重复(利用了递归),直至得到一个长度为n的有序序列为止,这种排序方法又被称为二路归并排序。
我们这里的案例也不例外,两种方式都是采用归并方法。
第一个方法采用递归归并求解,除了考察归并思想之外,还可以考察到“求链表中点”,“合并两个有序链表”的操作,这两个操作也都是LeetCode上的题目。第一种方法需要O(logn)的空间复杂度。
第二个方法采用非递归归并求解,这种方法只需要O(1)的空间复杂度,是最好的方式,当然更难理解。使用该方法的时候,需要注意在进行了部分链表合并排序之前,需要断开链表的联系,排序之后,需要将前后节点的引用关系再关联起来。
1 方法一
/**
* 148. 排序链表
* 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
* https://leetcode-cn.com/problems/sort-list/
* 中等
*/
public class LeetCode148
/**
* 自顶向下递归实现
* 空间复杂度:O(logn)
*/
public ListNode sortList(ListNode head)
/*
* 1 递归-拆分
*/
//递归到最深处的结束的条件
if (head == null || head.next == null)
return head;
//通过快慢指针找出中点,快指针每次走两步,慢指针每次走一步,当快指针走到了末尾,那么此时慢指针所在的位置就是中间位置
//求链表中间节点,这实际上就是LeetCode 876题:https://leetcode-cn.com/problems/middle-of-the-linked-list/
//唯一的区别就是这里我们让fast先走两步,然后fast走到终点的时候,slow就差一步到中点,下面进行递归的时候就采用
//slow.next作为右边的起点,同时方便后续断开两个链表的连接操作
ListNode slow = head, fast = head.next.next;
while (fast != null && fast.next != null)
slow = slow.next;
fast = fast.next.next;
// 对右半部分链表进行递归的分解,直到满足返回条件
ListNode right = sortList(slow.next);
//因为左右两边的链表现在已经独立了,所以需要将slow.next置为null,让他们真正的断开
//因为后面会再将两个子链表组合在一起,所有这一步很重要,真正的断开原来链表左右部分的联系,防止循环链表
slow.next = null;
// 对左半部分链表进行递归的分解,直到满足返回条件
ListNode left = sortList(head);
/*
* 2 排序-合并
*/
ListNode dummy = new ListNode(-1), cur = dummy;
//这里实际上就是对两个排序链表进行合并的过程,left代表左边链表的头部,right代表右边链表的头部
//两个排序链表合并,这实际上就是LeetCode 21题:https://leetcode-cn.com/problems/merge-two-sorted-lists/
while (left != null && right != null)
if (left.val < right.val)
cur.next = left;
left = left.next;
else
cur.next = right;
right = right.next;
cur = cur.next;
cur.next = left == null ? right : left;
return dummy.next;
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;
2 方法二
/**
* 148. 排序链表
* 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
* https://leetcode-cn.com/problems/sort-list/
* 中等
*/
public class LeetCode148
/**
* 自底向上非递归实现
* 空间复杂度:O(1)
*/
public ListNode sortList1(ListNode head)
//直接返回的条件
if (head == null || head.next == null)
return head;
//计算链表长度,并且获取尾部节点
int length = 0;
ListNode node = head;
while (node != null)
length++;
node = node.next;
//由于不确定head节点,因此使用哨兵节点
ListNode dummy = new ListNode(0, head);
//分割链表,实现自底向上的归并。subLength为分割单位,每次增长一倍。
//每次循环对当前分割轮次的链表进行两两合并排序,由于subLength起始值为1,因此最终合并之后的链表是有序的
for (int subLength = 1; subLength < length; subLength <<= 1)
//前驱节点和当前节点
ListNode pre = dummy, cur = dummy.next;
while (cur != null)
//获取当前分割单位长度的链表节点,作为左边部分
//左子链表的起始节点
ListNode left = cur;
for (int i = 1; i < subLength && cur.next != null; i++)
cur = cur.next;
//获取当前分割单位长度的链表节点,作为右边部分
//右子链表的起始节点
ListNode right = cur.next;
/*这一步很重要,就和递归实现的方法中一样,需要断开左右两个子链表的关系*/
cur.next = null;
cur = right;
for (int i = 1; i < subLength && cur != null && cur.next != null; i++)
cur = cur.next;
//获取右子链表尾部节点的后继,然后断开和后面的链表节点的关系
ListNode next = null;
if (cur != null)
next = cur.next;
cur.next = null;
//对这个左子链表和右子链表进行合并,并且将合并之后的链表头节点赋值给前驱节点的后继
//这样,这一部分的链表节点就有序了,然后进行下一部分排序
pre.next = merge(left, right);
//然后pre指向尾部节点,为下次循环做准备,向后推进
while (pre.next != null)
pre = pre.next;
//当前节点指向之前已经断开联系的后继,向后推进
cur = next;
return dummy.next;
public ListNode merge(ListNode left, ListNode right)
ListNode dummy = new ListNode(-1), cur = dummy;
//这里实际上就是对两个排序链表进行合并的过程,left代表左边链表的头部,right代表右边链表的头部
//两个排序链表合并,这实际上就是LeetCode 21题:https://leetcode-cn.com/problems/merge-two-sorted-lists/
while (left != null && right != null)
if (left.val < right.val)
cur.next = left;
left = left.next;
else
cur.next = right;
right = right.next;
cur = cur.next;
cur.next = left == null ? right : left;
return dummy.next;
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;
以上是关于LeetCode Java刷题笔记—148. 排序链表的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode Java刷题笔记—82. 删除排序链表中的重复元素 II