
双指针+归并排序!图解排序链表!
前言
大家好,我是程序员田螺,今天我们来看一道很经典的leetcode真题:排序链表
题目
给你链表的头结点head
,请将其按 升序 排列并返回 排序后的链表 。要求时间复杂度是O(n log n)
实例1:
实例2:
分析
排序算法选定
时间复杂度要求是O(n log n)
,我们很容易想到快速排序,以及归并排序。
我们先来回顾下快速排序
,它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
也就是说,快速排序需要找到基准值,一部分数据比这个基准值小,一部分数据比这个基准值大。因为这个是个链表,发现即使找完基准值,也不好操作。因此,可以考虑使用归并排序算法。
归并排序算法核心步骤
归并排序核心步骤如下:
- 把长度为n的要排序的序列,分成两个长度为n/2的子序列;
- 对这两个子序列,分别采用归并排序;
- 将两个排序好的子序列,合并成一个最终有序的排序序列。
对于链表来说,不同于一般的数据序列,它找到中间节点之后,需要切断一下。因此用归并排序算法,去排链表的操作大概是这样:
- 遍历链表,找到中间节点。
- 找到中间节点后,切断
- 分别再用归并排序,排左右子链表
- 合并子链表
使用归并排序算法,如何找到中间节点?
我们可以使用双指针法,即一个快指针,一个慢指针。
快指针每次走两步,而慢指针一次只走一步。当快指针走到链表末节点的时候,慢指针是不是走到中间节点啦。
因此,找到链表中间节点的代码,我们可以这么写:
找到中间节点后,切断
找到中间节点后,如何切断呢?
我们可以把slow
节点的next
指针,赋给新的变量mid
,然后再slow
的next
指针指向空,就可以切断啦。如图:
因此代码可以这么写:
分别再用归并排序,排左右子链表
假设我们本来的排序方法是sortList
,那我们找到head和mid子链表后,那需要用同样方法区排序这两个子链表嘛。
合并子链表
我们要合并两个都有序的子链表,把它变成一个新的有序的链表,其实可以借助一个中间链表,然后分别遍历左、右子链表,比较两个链表当前节点的值,哪个小的话,把它写到中间链表,并且值小的节点所在的链表,继续遍历下一个节点。
图解合并过程
我还是画个示意图吧,这样好理解一点。
假设要排序的左、右链表,以及中间链表如下:
首先取左链表的值1,与右链表的值3比较,因为1<3
,所以把值为1
的节点放到中间链表,并且左链表移动一个节点,中间链表也右移一个节点,如图:
接着呢,取左链表的值5,与右链表的值3比较,因为5>3
,所以把值为3
的节点放到中间链表,并且右链表移动一个节点,中间链表也移动一个节点,如图:
接下来呢,取左链表的值5,与右链表的值4比较,因为5>4
,所以把值为4
的节点放到中间链表,并且右链表移动一个节点,中间链表也移动一个节点,如图:
紧接者,取左链表的值5,与右链表的值6比较,因为5<6
,所以把值为5
的节点放到中间链表,并且左链表移动一个节点,中间链表也移动一个节点,如图:
最后,因为左链表已经遍历完啦,右链表还没有,因此把右链表放到中间链表即可(官方点来说,就是中间链表的next指针指向右链表),如图:
这时候,链表已经合并完啦,但是还不是我们想要的,因为多了个初始节点0
,并且指针指向了5
。怎么办呢?我们要的是1-> 3-> 4 ->5 ->6
。
我们可以初始化的时候,把链表head
赋给一个temp
嘛,然后让temp
去移动,最后返回head的next
就可以啦,如图:
合并代码实现
我们根据图解的过程,来实现下代码吧,如下:
如果代码还有不理解的地方,可以回头再看看图解过程哈。
完整代码实现
通过遍历链表找到中间节点、中间节点切断链表、分别用归并排序排左右子链表、合并子链表,我们可以整合一下,得出完整代码,如下:
去leetcode提交一下,找找成就感,通过啦,哈哈。
--end--
文章转载自公众号:捡田螺的小男孩
