LeetCode148 排序链表

本文解析了LeetCode 148题排序链表的两种归并排序方法:自顶向下与自底向上。自顶向下采用递归,空间效率较低;自底向上则通过逐步合并子链表,空间复杂度优化。详细介绍了每种方法的实现步骤和时间复杂度分析。
摘要由CSDN通过智能技术生成

题目

在这里插入图片描述

解题

题目对时间复杂度有要求,采用归并排序。建议先做一下 LeetCode21 合并两个有序链表

解题一:自顶向下归并排序

在这里插入图片描述
和官方解答的实现有些不同,首先判断是否满足两个终止条件,再去寻找左半截链表的结尾,快慢指针的方法在也有使用 LeetCode234 &《程序员面试金典》面试题 02.06. 回文链表,通过左半截链表的结尾找到寻找右半截链表的开头,接着一定要将 两段链表从中截断firstHalfEnd.next = null;)再分别排序后进行合并。

// javascript
var sortList = function(head) {
    if (head === null || head.next === null) {    // 链表为空 / 仅有一个节点,返回 head
        return head;
    }
    const firstHalfEnd = getfirstHalfEnd(head);   // 寻找左半截链表的结尾
    const secondHalfStart = firstHalfEnd.next;    // 寻找右半截链表的开头
    firstHalfEnd.next = null;                     // 将两段链表截断
    const l1 = sortList(head);                    // 对左半截链表进行排序
    const l2 = sortList(secondHalfStart);         // 对右半截链表进行排序
    return mergeTwoLists(l1, l2);    // 合并两个有序链表
};

// 寻找左半截链表的结尾
const getfirstHalfEnd = (head) => {
    if (head === null) return null; // 可以省去,因为调用前判断过 head 不为 null
    let fast = head, slow = head;
    while (fast.next !== null && fast.next.next !== null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
};

// 合并两个有序链表
const mergeTwoLists = (l1, l2) => {
    const mergedHead = new ListNode(0);
    let merged = mergedHead;
    while (l1 !== null && l2 !== null) {
        if (l1.val < l2.val) {
            merged.next = l1;
            l1 = l1.next;
        } else {
            merged.next = l2;
            l2 = l2.next;
        }
        merged = merged.next;
    }
    merged.next = l1 === null ? l2 : l1;
    return mergedHead.next;
};

根据主定理:
在这里插入图片描述
T ( n ) = 2 ∗ T ( n / 2 ) + n T(n) = 2*T(n/2) + n T(n)=2T(n/2)+n, a = 2, b = 2, d = 1, 2 = 2 ^ 1, 时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
在这里插入图片描述

解题二:自底向上归并排序

在这里插入图片描述
空间复杂度更优,思想是:先将长度为1的链表两两合并,再将长度为2的有序链表两两合并…但是对于链表而言,写起来比较繁琐,容易出错。

// javascript
var sortList = function(head) {
    if (head === null) return null;
    const length = getLength(head);
    const dummyHead = new ListNode(0, head);
    for (let subLength = 1; subLength < length; subLength <<= 1) {
        let prev = dummyHead, curr = dummyHead.next;
        while (curr !== null) {
            const head1 = curr;                                     // 找到 head1
            const head2 = moveAheadAndSplit(head1, subLength - 1);  // 找到 head2
            const next = moveAheadAndSplit(head2, subLength - 1);   // 找到下一段要处理的链表
            const merged = mergeTwoLists(head1, head2);             // 合并两个有序链表
            prev.next = merged;                                     // 将前面已被操作过的链表连上新合并好的链表
            while (prev.next !== null) {                            // 找到新的已被操作过的链表的最后一个非空节点
                prev = prev.next;
            }
            curr = next;                                            // curr 指向下一次需要操作的链表开头
        }
    }
    return dummyHead.next;
};

const getLength = (head) => {
    let length = 0;
    while (head !== null) {
        length++;
        head = head.next;
    }
    return length;
};

const moveAheadAndSplit = (head, count) => {
    // 找到 head 链表最后一个非空节点(有可能为空)
    while (count > 0 && head !== null) {
        head = head.next;
        count--;
    }
    let nextHead = null;
    if (head !== null) {           // 如果非空
        nextHead = head.next;      // 记录下一段链表开头
        head.next = null;          // 将该链表与下一段链表截断
    }
    return nextHead;               // 返回下一段链表开头
};

const mergeTwoLists = (l1, l2) => {
    const dummyHead = new ListNode(0);
    let cur = dummyHead;
    while (l1 !== null && l2 !== null) {
        if (l1.val < l2.val) {
            cur.next = l1;
            l1 = l1.next;
        } else {
            cur.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;
    }
    cur.next = (l1 === null) ? l2 : l1;
    return dummyHead.next;
};

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值