Bootstrap

LeetCode题练习与总结:重排链表--143

一、题目描述

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

L0 → L1 → … → Ln - 1 → Ln

请将其重新排列后变为:

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

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

示例 1:

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

示例 2:

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

提示:

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

二、解题思路

  1. 找到链表的中点:我们可以使用快慢指针的方法来找到链表的中点,快指针每次移动两步,慢指针每次移动一步,当快指针到达链表尾部时,慢指针就指向了链表的中点。

  2. 翻转链表的后半部分:将链表从中点分为两部分,翻转后半部分的链表。

  3. 合并链表:将前半部分和翻转后的后半部分交替合并,具体操作为,前半部分的当前节点指向后半部分的当前节点,然后前半部分的当前节点向后移动一位,后半部分的当前节点也向后移动一位,如此循环直到后半部分为空。

三、具体代码

class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }
        // 1. 找到链表的中点
        ListNode slow = head, fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        // 2. 翻转链表的后半部分
        ListNode mid = slow.next;
        slow.next = null;
        ListNode pre = null, cur = mid;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        // 3. 合并链表
        ListNode l1 = head, l2 = pre;
        while (l2 != null) {
            ListNode l1Next = l1.next;
            ListNode l2Next = l2.next;
            l1.next = l2;
            l2.next = l1Next;
            l1 = l1Next;
            l2 = l2Next;
        }
    }
}

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 找到链表的中点:使用快慢指针,时间复杂度为 O(n/2),即 O(n)。
  • 翻转链表的后半部分:翻转后半部分的链表,时间复杂度为 O(n/2),即 O(n)。
  • 合并链表:合并前半部分和翻转后的后半部分,时间复杂度为 O(n/2),即 O(n)。
  • 综上所述,总的时间复杂度为 O(n) + O(n) + O(n) = O(n)。
2. 空间复杂度
  • 找到链表的中点:使用快慢指针,不需要额外空间,空间复杂度为 O(1)。
  • 翻转链表的后半部分:翻转链表时,只需要几个指针变量,空间复杂度为 O(1)。
  • 合并链表:合并链表时,只需要几个指针变量,空间复杂度为 O(1)。
  • 综上所述,总的空间复杂度为 O(1)。

综合以上分析,该算法的时间复杂度为 O(n),空间复杂度为 O(1)。

五、总结知识点

  1. 链表的基本操作:链表是由节点组成的,每个节点包含数据和指向下一个节点的指针。在这个问题中,我们需要对链表进行遍历、分割、翻转和合并操作。

  2. 快慢指针:快慢指针是一种常用的技巧,用于找到链表的中点。快指针每次移动两步,慢指针每次移动一步,当快指针到达链表尾部时,慢指针就指向了链表的中点。

  3. 链表翻转:翻转链表是链表操作中的经典问题。通过改变节点的指向,将链表的节点顺序反转。这通常通过迭代的方式完成,需要维护前一个节点、当前节点和下一个节点的指针。

  4. 链表合并:将两个链表合并成一个链表,通常需要维护两个链表的当前节点,并按照一定的规则(如交替)将节点连接起来。

  5. 循环和递归:在链表的操作中,循环和递归是两种常用的遍历方式。在这个问题中,我们使用了循环来遍历链表,并执行翻转和合并操作。

  6. 节点拆分:在找到链表的中点后,需要将链表从中点拆分为两个独立的链表。这涉及到修改链表的指针,使前半部分的尾节点指向 null,并将后半部分作为新的链表。

  7. 边界条件处理:在编写链表操作代码时,需要特别注意边界条件,如链表为空、链表只有一个节点或两个节点的情况。这些特殊情况需要特别处理,以确保代码的正确性和鲁棒性。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

;