链表题目:重排链表

题目

标题和出处

标题:重排链表

出处:143. 重排链表

难度

5 级

题目描述

要求

给定一个单链表的头结点。单链表可以表示为:

L 0 → L 1 → … → L n   -   1 → L n \texttt{L}_\texttt{0} \rightarrow \texttt{L}_\texttt{1} \rightarrow \texttt{\ldots} \rightarrow \texttt{L}_\texttt{n - 1} \rightarrow \texttt{L}_\texttt{n} L0L1Ln - 1Ln

请将其重新排列后变为:

L 0 → L n → L 1 → L n   -   1 → L 2 → L n   -   2 → … \texttt{L}_\texttt{0} \rightarrow \texttt{L}_\texttt{n} \rightarrow \texttt{L}_\texttt{1} \rightarrow \texttt{L}_\texttt{n - 1} \rightarrow \texttt{L}_\texttt{2} \rightarrow \texttt{L}_\texttt{n - 2} \rightarrow \texttt{\ldots} L0LnL1Ln - 1L2Ln - 2

不能修改结点的值,只能修改结点本身。

示例

示例 1:

示例 1

输入: head   =   [1,2,3,4] \texttt{head = [1,2,3,4]} head = [1,2,3,4]
输出: [1,4,2,3] \texttt{[1,4,2,3]} [1,4,2,3]

示例 2:

示例 2

输入: head   =   [1,2,3,4,5] \texttt{head = [1,2,3,4,5]} head = [1,2,3,4,5]
输出: [1,5,2,4,3] \texttt{[1,5,2,4,3]} [1,5,2,4,3]

数据范围

  • 链表中结点的数目范围是 [1,   5 × 10 4 ] \texttt{[1, 5} \times \texttt{10}^\texttt{4}\texttt{]} [1, 5×104]
  • 1 ≤ Node.val ≤ 1000 \texttt{1} \le \texttt{Node.val} \le \texttt{1000} 1Node.val1000

解法一

思路和算法

由于链表不支持随机访问,因此可以使用数组存储链表中的每个结点,然后通过数组随机访问链表中的每个结点。

遍历链表,将每个结点依次加入数组,则数组中的结点顺序和原始链表的结点顺序相同。根据重新排列的规则,需要将原始链表的结点按照从外到内的顺序连接,因此可以定义两个下标 left \textit{left} left right \textit{right} right 分别指向首尾的待连接的结点,初始时 left \textit{left} left right \textit{right} right 分别为头结点和尾结点的下标。

重新排列链表的操作如下:

  1. left \textit{left} left 指向的结点的后一个结点定为 right \textit{right} right 指向的结点,然后将 left \textit{left} left 的值加 1 1 1

  2. 如果 left < right \textit{left} < \textit{right} left<right,将 right \textit{right} right 指向的结点的后一个结点定为 left \textit{left} left 指向的结点,然后将 right \textit{right} right 的值减 1 1 1

重复上述步骤直到 left \textit{left} left right \textit{right} right 相遇,此时 left \textit{left} left right \textit{right} right 都指向重新排列的链表的最后一个结点,需要将该节点的后一个结点定为 null \text{null} null,避免链表中出现环。

代码

class Solution {
    public void reorderList(ListNode head) {
        List<ListNode> nodes = new ArrayList<ListNode>();
        ListNode node = head;
        while (node != null) {
            nodes.add(node);
            node = node.next;
        }
        int left = 0, right = nodes.size() - 1;
        while (left < right) {
            nodes.get(left).next = nodes.get(right);
            left++;
            if (left < right) {
                nodes.get(right).next = nodes.get(left);
                right--;
            }
        }
        nodes.get(left).next = null;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要遍历链表中的每个结点一次,将结点加入数组,然后需要遍历数组中的每个结点一次,完成重新排列。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要创建长度为 n n n 的数组存储链表中的每个结点。

解法二

思路和算法

解法一需要使用数组存储每个结点,空间复杂度是 O ( n ) O(n) O(n)。也可以不使用数组存储结点,将空间复杂度降低到 O ( 1 ) O(1) O(1)

注意到如果将链表分成前一半和后一半,满足前一半和后一半的长度之差为 0 0 0 1 1 1,则重新排列链表的结果等价于将前一半和反转后的后一半合并之后的结果。

首先找到链表的中间结点。可以使用「链表的中间结点」的快慢指针的做法,但是具体实现有所变化,当链表的结点数是偶数时,得到的是链表的第一个中间结点。快慢指针遍历结束时,快指针 fast \textit{fast} fast 移动到链表的尾结点或者倒数第二个结点,慢指针 slow \textit{slow} slow 移动到链表的中间结点。

链表的后一半为 slow \textit{slow} slow 后面的部分,不包含 slow \textit{slow} slow。记 rightHead : = slow . next \textit{rightHead} := \textit{slow}.\textit{next} rightHead:=slow.next,则 rightHead \textit{rightHead} rightHead 为反转之前的后一半的头结点。得到 rightHead \textit{rightHead} rightHead 之后,令 slow . next : = null \textit{slow}.\textit{next} := \text{null} slow.next:=null,将前一半和后一半的连接断开。

确定链表的前一半和后一半之后,将后一半反转,即反转从 rightHead \textit{rightHead} rightHead 到末尾的部分。反转链表的做法可以使用「反转链表」的迭代解法。

将后一半反转之后,将前一半和后一半合并。由于前一半和后一半的长度之差最多为 1 1 1,因此可以直接合并。

代码

class Solution {
    public void reorderList(ListNode head) {
        if (head == null) {
            return;
        }
        ListNode fast = head, slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode rightHead = slow.next;
        slow.next = null;
        ListNode prev = null, curr = rightHead;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        ListNode node1 = head, node2 = prev;
        while (node1 != null && node2 != null) {
            ListNode temp1 = node1.next, temp2 = node2.next;
            node1.next = node2;
            node1 = temp1;
            node2.next = node1;
            node2 = temp2;
        }
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要使用快慢指针遍历链表,遍历链表的后一半并反转,以及反转后合并链表,每次遍历的时间复杂度都是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( 1 ) O(1) O(1)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值