先看LeetCode上面的题目:
给定链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
示例:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
思考:
这道题的解决办法有许多,网上都可以找到,那么在这里,我们就来使用 归并排序 把它做一下。
先来了解下什么是 归并排序:
归并排序的一个重要思想就是:分而治之
比如我有一组无序的整形数组,我想使用归并排序给它排个序,首先我会将这个整形数组分割成一个一个小的数组,一直分隔到小数组的元素个数为1为止。
然后从元素个数最少的数组开始将它们合并起来。最终全部合并完成之后,就是一个排序完成的数组。
当然我们这里的题目是对链表操作,但做法是一样的,没什么区别。
大体结构框架:
下面便来解决这道题:
为了方便,我们就在 IDEA 上编写这道题的代码,但为了使代码写完后能直接在 LeetCode 上运行,所用我们就使用 LeetCode 上的排版 。
整体框架:
当框架搭建完成后,具体步骤如下:
1.先判断是不是空链表。
2.再判断是不是只有一个节点(若只有一个节点,那么不用排了)
3.然后使用快慢指针进行链表的第一次分割。
4.分割好的两个链表又分别调用 sortList(ListNode head)函数,这样使用递归一层一层的往下分割,直至链表节点个数为1。
5.当分割完成之后,就需要进行合并操作,这里直接写一个合并的静态方法,然后return这个方法就行了。
具体代码:
package com.learn.java.test1;
/**
* 剑指offer - 077
* 给定链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
*/
public class Solution {
/**
* 合并方法的具体业务逻辑
* 这里使用递归,代码显得更加简洁,也更容易理解
* @param l1 第一条链表的头节点
* @param l2 第二条链表的头节点
* @return 合并后的链表头节点
*/
public ListNode merge(ListNode l1,ListNode l2){
//判断第一条链表为空链表的情况
if(l1 == null){
return l2;
}
//判断第二条链表为空链表的情况
if(l2 == null){
return l1;
}
//如果l1直线的节点的val值小于l2指向的val值
if(l1.val < l2.val){
//将头节点是l1.next的新链表与头节点为l2的原来的链表进行合并,
//合并后的链表同样连到l1节点后面
l1.next = merge(l1.next,l2);
//返回l1链表的头节点
return l1;
}else{
//反之,将l2节点后面一个节点开始的新链表与原l1链表合并,
//合并后同样连到l2节点后面
l2.next = merge(l1,l2.next);
//返回l2链表的头节点
return l2;
}
}
public ListNode sortList(ListNode head) {
//先判断是否是一个空链表
if(head == null){
return null;
//然后判断是否只有一个节点
}else if(head.next == null){
return head;
}else{
//使用快慢指针进行分割操作
ListNode slow = head; //慢指针
ListNode fast = head; //快指针
/*
这里注意我们在使用while循环时,里面的条件:
在分割时,我们不仅要拿到两条链表的头节点,还要将原来的链表真正的断开,
所以我们在拿到第二条链表的头节点时,也要拿到头节点的前一个节点(因为这是一个单链表)
最后将前一个节点的next置空(这一步很重要)
故fast指针需要满足两个条件:
1.fast为尾结点时停下来。
2.fast的下一个节点为尾结点时也要停下来。
(这里由于我们提前就判断了,所以不需要判断 fast != null)
*/
while( fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
//分割完成,l1和l2两条链表
ListNode l1 = head;
ListNode l2 = slow.next;
//将原本链表的联系给断开
slow.next = null;
//将l1和l2又分别进行递归分割
l1 = sortList(l1);
l2 = sortList(l2);
//最后将链表合并起来,返回头节点
return merge(l1,l2);
}
}
}
将代码放到 LeetCode 上进行测试:
明显测试通过。
如果想看一下代码的执行过程,那么我们也可以使用 IDEA 自己来测试一下:
这里注意想要用IDEA来测试的话,我们需要创建一个单链表。
单链表实体类代码:
package com.learn.java.test1;
/**
* 节点类
*/
class ListNode{
public final int val;
public ListNode next;
public ListNode(int data){
this.val = data;
}
}
/**
* 单链表
* 这里我们只需要稍微使用一下单链表,所以里面的功能不全
*/
public class MyLinkedList {
public ListNode head;
//尾插法
public void addLast(int data){
ListNode node = new ListNode(data);
//第一个节点
if(this.head == null){
this.head = node;
}else{
//第二个节点
ListNode cur = this.head;
while(cur.getNext() != null){
cur = cur.getNext();
}
cur.setNext(node);
}
}
//显示链表
public void display(){
ListNode cur = this.head;
while(cur != null){
System.out.print(cur.getVal()+" ");
cur = cur.getNext();
}
System.out.println();
}
//给定头节点节点显示链表
public void displayMyLinkedList(ListNode cur){
while(cur != null){
System.out.print(cur.getVal()+" ");
cur = cur.getNext();
}
System.out.println();
}
}
然后我们编写一个测试类来进行测试:
package com.learn.java.test1;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
MyLinkedList m = new MyLinkedList();
m.addLast(4);
m.addLast(2);
m.addLast(1);
m.addLast(3);
m.display();
System.out.println("--------------------");
Solution s = new Solution();
ListNode tmp = s.sortList(m.head);
m.displayMyLinkedList(tmp);
}
}
运行结果:
排序成功。