【前端面试常见算法题系列】143. 重排链表(中等)

760 阅读3分钟

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

一、题目描述

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

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

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

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

示例 1: image.png

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

示例 2: image.png

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

提示:

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

二、思路分析

题目要求重排链表,规则为 L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … ,以 head = [1,2,3,4,5] 为例,重排的链表为 1->5->2->4->3 ,也就是说要“反复横跳”。

这可为难了,链表又不像数组,想获取哪个元素就获取哪个元素,要是链表能变成数组就好了。

  1. 解法一:存储
    这么一想,好像有思路了,我们可以把链表存进元素为 ListNode 的数组中,这样就可以随时随地操作链表了。说干就干,思路如下:
  • 遍历链表,将遍历到的节点存进数组 vec 中。
  • 遍历结束之后,定义双指针,一个 i 在数组最前面,一个 j 在数组最后面。当 i < j 时,不断修改链表指向,vec[i++] -> next = vec[j]vec[j--] -> next = vec[i]

注意:当 i == j 时,就不用继续修改链表了,此时 vec[i] 已经是链表的末尾了。

  1. 解法二:寻找链表中点 + 链表逆序 + 合并链表 我们不难发现,所谓“反复横跳”,就是链表从最前面指向最后面、最后面指向次前面、次前面指向次后面...因此,我们可以
  • ①将链表分成两半(方便“反复横跳”)。使用快慢,就是指针,当快指针走到末尾时,返回慢指针,即为链表中点。
  • ②反转后半部分的链表(方便最前面指向最后面)。反转流程可参考我之前写过的题解:。
  • ③合并 前半部分链表 和 反转后的后半部分链表。前半部分的链表在前,轮流交替修改链表指向。 举个栗子直观展示:
要重排的链表为:1 -> 2 -> 3 -> 4 -> 5 -> 6

第一步,将链表平均分成两半
1 -> 2 -> 3
4 -> 5 -> 6
    
第二步,将第二个链表逆序
1 -> 2 -> 3
6 -> 5 -> 4
    
第三步,依次连接两个链表
1 -> 6 -> 2 -> 5 -> 3 -> 4

三、AC 代码

链表定义如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

解法一:存储

class Solution {
public:
    void reorderList(ListNode *head) {
        if (head == nullptr) {
            return;
        }
        vector<ListNode *> vec;
        ListNode *node = head;
        // 将 head 的每个节点存进数组中
        while (node != nullptr) {
            vec.emplace_back(node);
            node = node->next;
        }
        int i = 0, j = vec.size() - 1;
        while (i < j) {
            vec[i] -> next = vec[j];
            i++;
            if (i == j) {
                break;
            }
            vec[j] -> next = vec[i];
            j--;
        }
        vec[i]->next = nullptr; // 重排后的链表的末尾要指向 nullptr
    }
};

解法二:寻找链表中点 + 链表逆序 + 合并链表

class Solution {
public:
    void reorderList(ListNode* head) {
        if (!head) {
            return;
        }
        ListNode* mid = middleNode(head);
        ListNode* L = head;
        ListNode* R = mid -> next;
        R = reverseList(R);
        mid -> next = nullptr; // 重排后的链表的末尾要指向 nullptr
        mergeList(L, R);
    }

    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head, * fast = head;
        while (fast -> next && fast -> next -> next) {
            slow = slow -> next;
            fast = fast -> next -> next;
        }
        return slow;
    }

    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur) {
            ListNode* after = cur -> next;
            cur -> next = pre;
            pre = cur;
            cur = after;
        }
        return pre;
    }

    void mergeList(ListNode* L, ListNode* R) {
        ListNode* l1;
        ListNode* l2;
        while (L != nullptr && R != nullptr) {
            // 保存两个链表的后一个节点,方便后移
            l1 = L -> next;
            l2 = R -> next;

            L -> next = R;
            L = l1;

            R -> next = L;
            R = l2;
        }
    }
};