40、单链表的排序

描述
给定一个节点数为n的无序单链表,对其按升序排序。

数据范围:0<n≤1000000<n≤100000
要求:空间复杂度 O(n)O(n),时间复杂度 O(nlogn)O(nlogn)

示例1

输入:[1,3,2,4,5]

返回值:{1,2,3,4,5}

示例2

输入:[-1,0,-2]

返回值:{-2,-1,0}

方法一:归并排序(推荐使用)

知识点1:分治

分治即“分而治之”,“分”指的是将一个大而复杂的问题划分成多个性质相同但是规模更小的子问题,子问题继续按照这样划分,直到问题可以被轻易解决;“治”指的是将子问题单独进行处理。经过分治后的子问题,需要将解进行合并才能得到原问题的解,因此整个分治过程经常用递归来实现。

知识点2:双指针

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

思路:

前面我们做合并两个有序链表不是使用归并思想吗?说明在链表中归并排序也不是不可能使用,合并阶段可以参照前面这道题,两个链表逐渐取最小的元素就可以了,但是划分阶段呢?

常规数组的归并排序是分治思想,即将数组从中间个元素开始划分,然后将划分后的子数组作为一个要排序的数组,再将排好序的两个子数组合并成一个完整的有序数组,因此采用的是递归。而链表中我们也可以用同样的方式,只需要找到中间个元素的前一个节点,将其断开,就可以将链表分成两个子链表,然后继续划分,直到最小,然后往上依次合并。

终止条件: 当子链表划分到为空或者只剩一个节点时,不再继续划分,往上合并。
返回值: 每次返回两个排好序且合并好的子链表。
本级任务: 找到这个链表的中间节点,从前面断开,分为左右两个子链表,进入子问题排序。

怎么找中间元素呢?我们也可以使用快慢双指针,快指针每次两步,慢指针每次一步,那么快指针到达链表尾的时候,慢指针正好走了快指针距离的一半,为中间元素。

具体做法:

step 1:首先判断链表为空或者只有一个元素,直接就是有序的。
step 2:准备三个指针,快指针right每次走两步,慢指针mid每次走一步,前序指针left每次跟在mid前一个位置。三个指针遍历链表,当快指针到达链表尾部的时候,慢指针mid刚好走了链表的一半,正好是中间位置。
step 3:从left位置将链表断开,刚好分成两个子问题开始递归。
step 4:将子问题得到的链表合并,参考合并两个有序链表。
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     *
     * @param head ListNode类 the head node
     * @return ListNode类
     */


    //8.合并两段有序链表merge
    ListNode merge(ListNode pHead1, ListNode pHead2) {

        //9.一个已经为空了,直接返回另一个(pHead1为空返回pHead2,PHead2为空返回pHead1)
        if (pHead1 == null)
            return pHead2;
        if (pHead2 == null)
            return pHead1;
        //10.加一个表头head,并将表头给当前节点
        ListNode head = new ListNode(0);
        ListNode cur = head;
        //11.两个链表都要不为空(pHead1不为空并且pHead2不为空)
        while (pHead1 != null && pHead2 != null) {

            //12.取较小值的结点(比较pHead1的值与pHead2的值,当pHead1小于且等于pHead2的值时)
            if (pHead1.val <= pHead2.val) {


                //12.1将pHead1复制给cur的next
                cur.next = pHead1;

                //13.只移动取值的指针(pHead1的next赋值给pHead1)
                pHead1 = pHead1.next;
            }
            //14.只移动取值的指针(pHead2的next赋值给pHead2)
            else {
                cur.next = pHead2;
                pHead2 = pHead2.next;
            }

            //15.指针后移(cur指针)
            cur = cur.next;
        }

        //16.哪个链表还有剩,直接连在后面
        //16.1如果pHead1不为空,将cur的nexr为pHead1
        if (pHead1 != null)
            cur.next = pHead1;
        //16.2如果pHead2不为空,将cur.next为pHead2
        else
            cur.next = pHead2;
        //17.返回值去掉表头(即为返回的是head的next)
        return head.next;


    }

    public ListNode sortInList (ListNode head) {
        // write code here
        //1.链表为空或者只有一个元素,直接就是有序(当head或者head.next为空时)
        if (head == null || head.next == null)
            return head;

        //2.左节点为头结点
        ListNode left = head;

        //3.中间结点为头结点的next
        ListNode mid = head.next;

        //4.右结点为头结点的next的next
        ListNode right = head.next.next;

        //5.右边的指针到达末尾时,中间的指针指向该端链表的中间
        //5.1当右指针不为为空并且有指针的next不为空的时候
        while (right != null && right.next!= null) {
            //5.2left和mid走一步,right走两步
            left = left.next;
            mid = mid.next;
            right = right.next.next;

        }
        //6.左边指针指向左段的左右一个节点,从这里断开
        left.next = null;
        //7.分成两段排序,合并排好序的两段(两段的头结点分别为head,mid)
        return merge(sortInList(head), sortInList(mid));


    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值