[路飞]_leetcode刷题_148. 排序链表

134 阅读3分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战

题目

148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:

image.png

输入: head = [4,2,1,3]
输出: [1,2,3,4]

示例 2:

image.png

输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]

示例 3:

输入: head = []
输出: []

解法

归并排序。

思路

链表排序最好的伙伴就是归并排序。我们可以回忆之前做合并两个有序链表的时候,用的方法其实就是归并排序的思想。

我们回忆一下合并两个有序链表的过程。

  1. 创建一个假头结点,新建一个指针指向它。
  2. 同时遍历两个链表,比较链表当前节点的值
  3. 谁的值小,就让假头节点的next指向谁。然后假头结点向前走,值较小的链表节点向后移。
  4. 知道有一条链表的节点被遍历完,此次遍历结束。
  5. 然后再看两个链表,是否还有剩余的节点,谁剩余节点就将当前的假头节点的指针指向他,等于就是将这条链表的节点全都接到我们这个假头节点的链表上去。
  6. 最后返回假头节点的下一个节点作为排序完成的新链表的头节点即可。

那么合并的方案已经有了,剩下的就是怎么去创建两个有序的链表呢?

就按归并排序的思想来。

  1. 递归排序整个链表
  2. 使用快慢指针遍历链表,求得中间节点
  3. 先排序左链表,再排序右链表
  4. 这会再次进入递归方法,递归方法退出的条件就是,传入的头节点的下一个节点就是尾节点。
  5. 然后再将左右两个排序好的链表合并即可。
  6. 这个过程是递归进行的。

代码

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var sortList = function(head) {
    // 判断head非空
    if(head === null){
        return head;
    }
    // 递归排序该链表
    return dfs(head,null);
};

// 递归的去处理[head,tail)之间的链表节点,将其排为有序
function dfs(head,tail){
    // 如果[head,tail)之间只有一个节点,即head.next = tail,
    // 则直接返回head,并切断head和tail的联系,不用排序
    if(head.next == tail){
        head.next = null;
        return head;
    }
    // 计算出中间节点mid
    // 满足如下
    // all in [head, mid) is left
    // all in [mid,right) is right
    // 用快慢指针的方法来计算
    let slow = head;
    let fast = head;
    // 如果fast到达了尾节点,则退出循环
    while(fast != tail){
        fast = fast.next;
        slow = slow.next;
        // 如果fast还行往前走,则往前走
        if(fast != tail){
            fast = fast.next;
        }
    }
    let mid = slow;
    // 递归排序左链表
    let left = dfs(head,mid);
    // 递归排序右链表
    let right = dfs(mid,tail)
    // 合并两个有序链表链表
    return merge(left,right);
}

// 合并有序链表,其实方法和合并有序数组是一样的
function merge(head1,head2){
    let dummyNode = new ListNode(0);
    let temp = dummyNode;
    let temp1 = head1;
    let temp2 = head2;
    // 遍历退出条件是任何一个链表到了尾结点了
    while(temp1 != null && temp2 != null ){
        // 谁小谁就往temp节点上接
        if(temp1.val < temp2.val){
            temp.next = temp1;
            temp1 = temp1.next;
        }else{
            temp.next = temp2;
            temp2 = temp2.next;
        }
        temp = temp.next;
    }
    // 结束后,谁有多余的都直接往temp上接
    if(temp1 != null){
        temp.next = temp1;
    }else{
        temp.next = temp2;
    }
    return dummyNode.next;
}

复杂度分析

时间复杂度:O(nlogn),递归栈的高度为logn,合并操作的评估复杂度为n,故为O(nlogn)。

空间复杂度:O(logn),即为递归栈的高度。