前端算法第一四四期-重排链表

116 阅读2分钟

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

给定一个单链表 L **的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

图片.png

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

示例 2:

图片.png

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

提示:

  • 链表的长度范围为 [1, 5 * 104]
  • 1 <= node.val <= 1000

双指针(翻转 + 插入)

  • 把链接分成两半:寻找中间节点,使用快慢指针,快指针一次走两步,慢指针一次走一步,当快指针走到链表尾节点时慢指针刚好走到链表的中间节点

    • 当链表的节点总数是奇数时,要确保链表的前半段比后半段多一个节点
  • 反转链表的后半段

  • 将两个链表依次相链

在初始化方面

  • slow 为第一个结点开始
  • fast 为第二个结点开始

在分成两半时

  • fast 走两步,不能一次性走两步,要一步一步走
  • 对于 fast 第二步,需要先判断,如果 fast 后面还可以再走一步,才往后走
/**
 * 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 {void} Do not return anything, modify head in-place instead.
 */
var reorderList = function(head) {
    if(!head.next) return head;

    let slow = head, fast = head.next; // slow 为第一个结点开始,fast 为第二个结点开始,这样结点总个数奇数或偶数都很好兼容
    // fast 走两步,不能一次性走两步,要一步一步走
    while(fast && fast.next) {
        slow = slow.next;
        fast = fast.next;
        // 对于 fast 第二步,需要先判断,如果 fast 后面还可以再走一步,才往后走
        if(fast.next) {
            fast = fast.next;
        }
    }

    // 现在 slow 和 fast 之间就是要翻转的部分了 (slow, fast]
    let newHead = null, p = slow.next, q;
    slow.next = null; // 前半部分和后半部分断开联系
    while(p) {
        q = p;
        p = p.next;
        q.next = newHead;
        newHead = q;
    }

    // 现在 newHead 就是后半段翻转后的样子,然后接下来跟前半部分 依次 插入新链表
    let resultHead = new ListNode(0), now = resultHead, flag = true;
    slow = head, fast = newHead;
    while(slow || fast) {
        // 通过 flag 控制现在插入 前半部分 还是 后半部分
        if(flag) {
            now.next = slow;
            slow = slow.next;
        } else {
            now.next = fast;
            fast = fast.next;
        }
        now = now.next;
        flag = !flag;
    }
    return resultHead.next;
};