题目内容
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
java普通解答(效率低,但是简单)
/**
* 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) {
ArrayList<Integer> arr = new ArrayList<>();
ListNode newhead=head;
if(head==null){
return head;
}
//循环遍历链表把值存进到arrayList中去
while (newhead!=null){
arr.add(newhead.val);
newhead=newhead.next;
}
newhead=head;
//调用集合工具类,实现对arrlist数组进行排序 默认升序
Collections.sort(arr);
//然后改变每个节点的值.
for (int i = 0; i < arr.size(); i++) {
newhead.val=arr.get(i);
newhead=newhead.next;
}
return head;
}
}
归并排序(自顶向下归并排序)
leetcode官方:
考虑到递归调用的栈空间,自顶向下归并排序的空间复杂度是 O(logn)O(\log n)O(logn)。如果要达到 O(1)O(1)O(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) {
//没有节点或者只有一个节点的时候直接返回(递归终止条件)
if(head==null||head.next==null){
return head;
}
//快慢指针找中间节点
ListNode slow=head;
ListNode fast=head;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode right=sortList(slow.next); //让右边链表进行排序
slow.next=null; //为了让链表从中间断开,断开后才可以排序左边的..
ListNode left=sortList(head);//让左边链表进行排序
ListNode newHead=merge(left,right); //合并两个链表,返回头节点
return newHead;
}
public ListNode merge(ListNode left, ListNode right) {
//申请个虚拟结点,放最后返回头结点用
ListNode dummy=new ListNode(0);
//改指针用来走
ListNode cur=dummy;
while(left!=null&&right!=null){ //遍历两个链表
if(left.val<right.val){ //如果左边的值小,就让cur.next指向左边,同时cur向后移动;;
cur.next=left;
cur=left;
left=left.next; //left向后移动
}else{ //同理右边大
cur.next=right;
cur=right;
right=right.next;
}
}
if(left!=null){ //如果左边链表没空,那么cur.next直接指向left
cur.next=left;
}else{ //右边不为空,或为空都走下面这句话 为空直接指向null,不需要单独区分
cur.next=right;
}
return dummy.next;
}
}
力扣官方题解(我加了注释,把merge变动了一点点)
class Solution {
public ListNode sortList(ListNode head) {
if (head == null||head.next==null) {
return head;
}
int length = 0; //求链表的长度
ListNode node = head;
while (node != null) {
length++;
node = node.next;
}
ListNode dummyHead = new ListNode(0, head);
for (int subLength = 1; subLength < length; subLength <<= 1) { //每次从底往上归并,每次分的组上组的2倍
ListNode prev = dummyHead, curr = dummyHead.next;
while (curr != null) {
ListNode head1 = curr;
for (int i = 1; i < subLength && curr.next != null; i++) {
curr = curr.next; //让cur指向当前段的最后一个节点
}
ListNode head2 = curr.next; //第二个组的的第一个节点
curr.next = null; //让第一组和第二组之间断开
curr = head2;
for (int i = 1; i < subLength && curr != null && curr.next != null; i++) {
curr = curr.next; //如果还有第三组,那么cur出来for循环后cur指向第三组第一个节点,没有组了那么cur指向null
}
ListNode next = null;
if (curr != null) { //如果有第三组
next = curr.next; //让next指向第三组的第一个节点
curr.next = null; //让第二组和第三组之间断开联系
}
ListNode merged = merge(head1, head2); //合并一二组
prev.next = merged; //第一次while循环时候让他先指向第一个节点 //第二遍循环的时候让第二组和三组之间连接起来
while (prev.next != null) { //让prev走到一二组合并后的最后一个节点 第二遍循环的时候是走到第四组的最后个节点
prev = prev.next;
}
curr = next;
}
//出来for循环后,这时候链表已经连接起来且是有序的
}
return dummyHead.next;
}
public ListNode merge(ListNode left, ListNode right) {
//申请个虚拟结点,放最后返回头结点用
ListNode dummy=new ListNode(0);
//改指针用来走
ListNode cur=dummy;
while(left!=null&&right!=null){ //遍历两个链表
if(left.val<right.val){ //如果左边的值小,就让cur.next指向左边,同时cur向后移动;;
cur.next=left;
cur=left;
left=left.next; //left向后移动
}else{ //同理右边大
cur.next=right;
cur=right;
right=right.next;
}
}
if(left!=null){ //如果左边链表没空,那么cur.next直接指向left
cur.next=left;
}else{ //右边不为空,或为空都走下面这句话 为空直接指向null,不需要单独区分
cur.next=right;
}
return dummy.next;
}
}
总结
多自己动手实现几遍归并,你会发现熟练度会越来越高,所以许多算法打的多了就不会出现一看就会,一写就废.