LeetCode 第 234 题「回文链表」
字数 1873 2025-10-25 22:55:12

好的,我们这次来详细讲解 LeetCode 第 234 题「回文链表」


1. 题目描述

题目:给你一个单链表的头节点 head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false

示例 1
输入:head = [1,2,2,1]
输出:true

示例 2
输入:head = [1,2]
输出:false

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?


2. 题意理解

回文的意思是正着读和反着读一样。
对于链表来说,我们需要判断从前往后遍历和从后往前遍历得到的序列是否一致。

难点
链表是单向的,只能从头往后遍历,不能直接反向遍历。
所以我们需要一种方法,能够比较“前半部分”和“反转后的后半部分”。


3. 思路分析

3.1 直观思路(但空间 O(n))

  • 遍历链表,把值存入数组,然后用双指针判断数组是否回文。
  • 时间复杂度 O(n),空间复杂度 O(n)(因为数组存储)。
  • 这个方法简单,但空间不是 O(1),不满足进阶要求。

3.2 满足 O(1) 空间的思路

我们可以分三步:

  1. 找到链表的中间节点(快慢指针法)。
  2. 反转链表的后半部分
  3. 比较前半部分和反转后的后半部分是否一致。
  4. (可选)恢复链表结构(题目一般不要求,但好习惯)。

4. 详细步骤

4.1 步骤 1:快慢指针找中点

  • 快指针每次走两步,慢指针每次走一步。
  • 当快指针到达末尾时,慢指针就在中间(或中间偏左,取决于链表长度奇偶)。
  • 对于偶数长度 1→2→2→1,慢指针会停在第一个 2,然后我们取慢指针的下一个作为后半部分头。
  • 对于奇数长度 1→2→3→2→1,慢指针停在 3,后半部分从 3 的下一个开始(即第二个 2),中间节点 3 在比较时忽略。

关键:我们想要把链表分成前后两半,后半部分反转后与前半部分比较。


4.2 步骤 2:反转后半部分链表

  • 从中间节点的下一个节点开始反转(偶数情况)或中间节点下一个(奇数情况)。
  • 反转链表的方法(迭代):
    • 初始化 prev = null, curr = 后半部头
    • 每次记录 nextTemp = curr.nextcurr.next = prevprev = currcurr = nextTemp
    • 直到 curr 为 null,prev 就是反转后的头。

4.3 步骤 3:比较前后两部分

  • 用两个指针,一个从原链表头 head,一个从反转后的后半部头。
  • 逐个比较节点值,如果全部相等则是回文。
  • 注意奇数长度时,前半部分比后半部分多一个中间节点,比较时忽略中间节点(后半部分先遇到结尾)。

4.4 步骤 4:恢复链表(可选)

  • 再次反转后半部分,接回原链表中间节点之后,保持链表原样。

5. 举例说明

例子 1:1 → 2 → 2 → 1

步骤 1:找中间节点

  • 快慢指针初始都指向 1。
  • 快一次走两步,慢一次一步:
    • 慢:1,快:1
    • 慢:2,快:2
    • 慢:2,快:null(快指针走两步时,第一步到 2,第二步到 null,结束)
  • 慢指针停在第一个 2(这是前半部分的尾)。
  • 后半部分头 = 慢指针.next = 第二个 2。

步骤 2:反转后半部分

  • 后半部分 2 → 1 反转后为 1 → 2,新头是 1。

步骤 3:比较

  • 前半部分:1 → 2
  • 反转后的后半部分:1 → 2
  • 逐个比较:1=1,2=2,全部相等 → 是回文。

例子 2:1 → 2 → 3 → 2 → 1

步骤 1:找中间节点

  • 快慢指针:
    • 慢:1,快:1
    • 慢:2,快:3
    • 慢:3,快:1(快指针从 3 走两步:第一步到 2,第二步到 1)
  • 慢指针停在 3(前半部分尾)。
  • 后半部分头 = 慢指针.next = 第二个 2。

步骤 2:反转后半部分

  • 后半部分 2 → 1 反转后为 1 → 2

步骤 3:比较

  • 前半部分:1 → 2(3 是中间节点,不参与比较)
  • 反转后的后半部分:1 → 2
  • 逐个比较:1=1,2=2 → 是回文。

6. 代码实现(关键部分)

def isPalindrome(head):
    if not head or not head.next:
        return True
    
    # 1. 快慢指针找中点
    slow, fast = head, head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    
    # 2. 反转后半部分
    prev = None
    curr = slow  # 从中间节点开始反转(包括中间节点)
    while curr:
        next_temp = curr.next
        curr.next = prev
        prev = curr
        curr = next_temp
    
    # 3. 比较前后部分
    first, second = head, prev
    while second:  # 后半部分可能比前半部分短(奇数长度时)
        if first.val != second.val:
            return False
        first = first.next
        second = second.next
    
    # 4. 可选:恢复链表(再次反转后半部分)
    # 本题一般不要求,略过。
    
    return True

7. 复杂度分析

  • 时间复杂度:O(n),遍历几次链表,都是线性。
  • 空间复杂度:O(1),只用了几个指针。

8. 总结

这道题结合了:

  • 快慢指针找链表中点
  • 反转链表
  • 双指针比较

核心技巧在于将后半部分反转,从而可以在 O(1) 空间内实现回文判断。
这是一个非常经典的链表综合题,希望这个逐步讲解能帮助你彻底理解!

好的,我们这次来详细讲解 LeetCode 第 234 题「回文链表」 。 1. 题目描述 题目 :给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。 示例 1 : 输入: head = [1,2,2,1] 输出: true 示例 2 : 输入: head = [1,2] 输出: false 进阶 :你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题? 2. 题意理解 回文 的意思是正着读和反着读一样。 对于链表来说,我们需要判断从前往后遍历和从后往前遍历得到的序列是否一致。 难点 : 链表是单向的,只能从头往后遍历,不能直接反向遍历。 所以我们需要一种方法,能够比较“前半部分”和“反转后的后半部分”。 3. 思路分析 3.1 直观思路(但空间 O(n)) 遍历链表,把值存入数组,然后用双指针判断数组是否回文。 时间复杂度 O(n),空间复杂度 O(n)(因为数组存储)。 这个方法简单,但空间不是 O(1),不满足进阶要求。 3.2 满足 O(1) 空间的思路 我们可以分三步: 找到链表的中间节点 (快慢指针法)。 反转链表的后半部分 。 比较前半部分和反转后的后半部分 是否一致。 (可选)恢复链表结构(题目一般不要求,但好习惯)。 4. 详细步骤 4.1 步骤 1:快慢指针找中点 快指针每次走两步,慢指针每次走一步。 当快指针到达末尾时,慢指针就在中间(或中间偏左,取决于链表长度奇偶)。 对于偶数长度 1→2→2→1 ,慢指针会停在第一个 2,然后我们取慢指针的下一个作为后半部分头。 对于奇数长度 1→2→3→2→1 ,慢指针停在 3,后半部分从 3 的下一个开始(即第二个 2),中间节点 3 在比较时忽略。 关键 :我们想要把链表分成前后两半,后半部分反转后与前半部分比较。 4.2 步骤 2:反转后半部分链表 从中间节点的下一个节点开始反转(偶数情况)或中间节点下一个(奇数情况)。 反转链表的方法(迭代): 初始化 prev = null , curr = 后半部头 。 每次记录 nextTemp = curr.next , curr.next = prev , prev = curr , curr = nextTemp 。 直到 curr 为 null, prev 就是反转后的头。 4.3 步骤 3:比较前后两部分 用两个指针,一个从原链表头 head ,一个从反转后的后半部头。 逐个比较节点值,如果全部相等则是回文。 注意奇数长度时,前半部分比后半部分多一个中间节点,比较时忽略中间节点(后半部分先遇到结尾)。 4.4 步骤 4:恢复链表(可选) 再次反转后半部分,接回原链表中间节点之后,保持链表原样。 5. 举例说明 例子 1: 1 → 2 → 2 → 1 步骤 1:找中间节点 快慢指针初始都指向 1。 快一次走两步,慢一次一步: 慢:1,快:1 慢:2,快:2 慢:2,快:null(快指针走两步时,第一步到 2,第二步到 null,结束) 慢指针停在第一个 2(这是前半部分的尾)。 后半部分头 = 慢指针.next = 第二个 2。 步骤 2:反转后半部分 后半部分 2 → 1 反转后为 1 → 2 ,新头是 1。 步骤 3:比较 前半部分: 1 → 2 反转后的后半部分: 1 → 2 逐个比较:1=1,2=2,全部相等 → 是回文。 例子 2: 1 → 2 → 3 → 2 → 1 步骤 1:找中间节点 快慢指针: 慢:1,快:1 慢:2,快:3 慢:3,快:1(快指针从 3 走两步:第一步到 2,第二步到 1) 慢指针停在 3(前半部分尾)。 后半部分头 = 慢指针.next = 第二个 2。 步骤 2:反转后半部分 后半部分 2 → 1 反转后为 1 → 2 。 步骤 3:比较 前半部分: 1 → 2 (3 是中间节点,不参与比较) 反转后的后半部分: 1 → 2 逐个比较:1=1,2=2 → 是回文。 6. 代码实现(关键部分) 7. 复杂度分析 时间复杂度:O(n),遍历几次链表,都是线性。 空间复杂度:O(1),只用了几个指针。 8. 总结 这道题结合了: 快慢指针找链表中点 反转链表 双指针比较 核心技巧在于 将后半部分反转 ,从而可以在 O(1) 空间内实现回文判断。 这是一个非常经典的链表综合题,希望这个逐步讲解能帮助你彻底理解!