时月oe

博客园 首页 新随笔 联系 订阅 管理

题目来源:排序链表

image

就是对链表进行排序,题目中另外一个要求就是使用\(O(nlogn)\)时间复杂度的算法,对于链表排序,比较好的办法就是递归排序


基本的算法流程如下

  • 确定链表中点(可以使用快慢指针法)
  • 将链表对半分,分别排序
  • 有序链表合并

1、快慢指针
对于一个链表,想要获取其中点,可以定义两个指针,一个快指针,一个慢指针。初始化全部指向头节点,然后快指针每次移动两个,慢指针每次移动一个,当快指针到达尾部的时候,慢指针就在中间了

2、有序链表合并
这个其实在leetcode上有专门的一道题:合并两个有序链表
也可以参考我的另一个文章:合并两个有序链表
有递归和非递归的版本,由于这道题目相对于合并链表那道题数据量要大很多,实际测试发现使用非递归版本更快

自顶向下归并排序

使用归并排序需要注意的就是,当你找到了链表的重点,要记得斩断链表,方便使用Merge进行归并

点击查看代码
/**
 * 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 process(ListNode head){
        if(head.next == null)return head;

        //快慢指针法 寻找链表中点
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null){
            fast = fast.next;
            if(fast != null) fast = fast.next;
            if(fast != null)slow = slow.next;
        }
        
        ListNode second = slow.next;      //第二段链表的开始
        slow.next = null;      //斩断两部分
        ListNode left = process(head);
        ListNode right = process(second);
        return Merge(left,right);
    }

    public ListNode Merge(ListNode list1,ListNode list2){
        ListNode ans = new ListNode();
        ListNode prev = ans;

        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                prev.next = list1;      //将前面排序好的节点的next指向list
                list1 = list1.next;
                prev = prev.next;
            }else{
                prev.next = list2;
                list2 = list2.next;
                prev = prev.next;
            }
        }
        if(list1 != null)prev.next = list1;
        if(list2 != null)prev.next = list2;
        return ans.next;
    }
    public ListNode sortList(ListNode head) {
        return head == null ? head : process(head);
    }
}

注意这段代码:

ListNode fast = head;
ListNode slow = head;
while(fast != null){
    fast = fast.next;
    if(fast != null) fast = fast.next;
    if(fast != null)slow = slow.next;
}

如果使用这段代码却会报错

ListNode fast = head;
ListNode slow = head;
while(fast != null){
    fast = fast.next;
    if(fast != null){
        fast = fast.next;
        slow = slow.next;
    }
}

看起来似乎是一样的,但是第二种是错误的,会死递归

快慢指针法用来找中点,然后进行合并
但是要保证一点:节点数为奇数,slow指向中点;节点数为偶数,slow指向中点左边那个

保证这个条件的目的就是:假如分到最后,最后一段链表长度为2,要保证可以分成两段
如果采用第二种方式,slow就会永远指向第二个节点,就会造成链表一直没办法被分成两段,从而死递归

ListNode second = slow.next;      //第二段链表的开始
slow.next = null;      //斩断两部分
ListNode left = process(head);
ListNode right = process(second);
return Merge(left,right);

所以一定要保证fast动了两个以后,slow再动

posted on 2022-07-19 18:05  时月oe  阅读(150)  评论(0)    收藏  举报