[路飞]算法——归并排序链表

171 阅读4分钟

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

今天学习排序链表,记录一波优秀题解

image.png

【题解】

方法一:归并排序 (递归法)

  • 通过递归实现链表归并排序,有以下两个环节:

    • 分割 cut 环节:
      1. 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
      2. 我们使用 fast, slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
      3. 找到中点 slow 后,执行 slow.next = null 将链表切断。
      4. 递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 mid (因为链表是从 slow 切断的)。
    • cut 递归终止条件: 时间复杂度 O(l + r)l, r 分别代表两个链表长度。
      1. head.next == null 时,说明只有一个节点了,直接返回此节点。
      2. 合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
      3. 双指针法合并,建立辅助ListNode newHead 作为头部。
      4. 设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
      5. 返回辅助ListNode newHead 作为头部的下个节点 newHead.next

image.png 【代码】


/**
 * 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) {
  if (head === null || head.next === null) return head

  // 找到中点 midNode 后,执行 midNode.next = null 将链表切断。
  // 递归分割时,输入当前链表左端点 head 和中心节点 midNode 的下一个节点 rightHead(因为链表是从 midNode 切断的)。
  let midNode = getMiddleNode(head)
  let rightHead = midNode.next
  midNode.next = null

  // 合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
  // 时间复杂度 O(l + r),l, r 分别代表两个链表长度。

  // 设置两指针 left, right 分别指向两链表头部
  let left = sortList(head)
  let right = sortList(rightHead)

  return mergeTwoLists(left, right)

};

// 找到链表中间节点
function getMiddleNode(head) {
  // 分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
  // 我们使用 fast,slow 快慢双指针法
  let fast = head.next, slow = head
  // 奇数个节点找到中点,偶数个节点找到中心左边的节点。
  while (fast !== null && fast.next !== null) {
    fast = fast.next.next
    slow = slow.next
  }
  // fast 走两步,slow走一步,fast走完时,slow在中点
  return slow
}

// 合并两个有序链表
function mergeTwoLists(left, right) {
  // 双指针法合并,建立辅助ListNode newHead 作为头部。
  let newHead = new ListNode()
  let ret = newHead

  // 比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
  while (left !== null && right !== null) {
    if (left.val < right.val) {
      newHead.next = left
      left = left.next
    } else {
      newHead.next = right
      right = right.next
    }
    newHead = newHead.next
  }
  newHead.next = left !== null ? left : right
  // 返回辅助ListNode newHead 作为头部的下个节点 newHead.next。
  return ret.next
}

方法二:归并排序(从底至顶直接合并)

【题解】

首先求得链表的长度 length,然后将链表拆分成子链表进行合并。

具体做法如下。

  • subLength 表示每次需要排序的子链表的长度,初始时 subLength=1

  • subLength 的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于 length,整个链表排序完毕。

如何保证每次合并之后得到的子链表都是有序的呢?可以通过数学归纳法证明。

  • 初始时 subLength=1,每个长度为 1 的子链表都是有序的。

  • 如果每个长度为 subLength 的子链表已经有序,合并两个长度为 subLength 的有序子链表,得到长度为 subLength×2 的子链表,一定也是有序的。

  • 当最后一个子链表的长度小于 subLength 时,该子链表也是有序的,合并两个有序子链表之后得到的子链表一定也是有序的。

【代码】

/**
 * 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) {
    if (head === null) {
        return head;
    }
    let length = 0;
    let node = head;
    while (node !== null) {
        length++;
        node = node.next;
    }
    const dummyHead = new ListNode(0, head);
    for (let subLength = 1; subLength < length; subLength <<= 1) {
        let prev = dummyHead, curr = dummyHead.next;
        while (curr !== null) {
            let head1 = curr;
            for (let i = 1; i < subLength && curr.next !== null; i++) {
                curr = curr.next;
            }
            let head2 = curr.next;
            curr.next = null;
            curr = head2;
            for (let i = 1; i < subLength && curr != null && curr.next !== null; i++) {
                curr = curr.next;
            }
            let next = null;
            if (curr !== null) {
                next = curr.next;
                curr.next = null;
            }
            const merged = mergeTwoLists(head1, head2);
            prev.next = merged;
            while (prev.next !== null) {
                prev = prev.next;
            }
            curr = next;
        }
    }
    return dummyHead.next;
};
// 合并两个有序链表
function mergeTwoLists(left, right) {
  // 双指针法合并,建立辅助ListNode newHead 作为头部。
  let newHead = new ListNode()
  let ret = newHead

  // 比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
  while (left !== null && right !== null) {
    if (left.val < right.val) {
      newHead.next = left
      left = left.next
    } else {
      newHead.next = right
      right = right.next
    }
    newHead = newHead.next
  }
  newHead.next = left !== null ? left : right
  // 返回辅助ListNode newHead 作为头部的下个节点 newHead.next。
  return ret.next
}