链表题解——回文链表【LeetCode】

一、算法逻辑(通顺讲解每一步思路)

我们从 isPalindrome 这个主函数入手:

步骤 1:找到链表的中间节点 middleNode

  • 使用 快慢指针法(slow 和 fast)

  • 快指针一次走两步,慢指针一次走一步。

  • 当快指针走到链表末尾时,慢指针刚好到达链表中点。

  • 目的:将链表划分成前后两部分。

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

  • 从中点开始,就地反转链表

  • 通过不断修改 next 指针,实现链表的反转。

  • 最终得到后半段链表的头指针 head2

步骤 3:比较前半部分和反转后的后半部分是否相等

  • 现在前半部分从 head 开始,后半部分从 head2 开始。

  • 每次比较两个节点的值是否相等。

  • 若有一对值不同,说明不是回文,立即返回 False

  • 若后半段全部匹配完了,说明是回文链表,返回 True


二、核心点

  1. 快慢指针找中点 是核心技巧之一,它在 O(n) 时间内找出链表中点,无需额外空间。

  2. 就地反转链表 则是另一个关键,它允许我们不借助栈或数组完成后半段反转,依然保持 O(1) 空间复杂度。

  3. 最后通过同时遍历前半部分与后半部分,实现了线性时间的比较。

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    # 876. 链表的中间结点
    def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

    # 206. 反转链表
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        pre, cur = None, head
        while cur:
            nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt
        return pre

    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        mid = self.middleNode(head)
        head2 = self.reverseList(mid)
        while head2:
            if head.val != head2.val:  # 不是回文链表
                return False
            head = head.next
            head2 = head2.next
        return True

三、时间复杂度分析

设链表长度为 n

  • middleNode(head):遍历一次链表的一半,O(n)

  • reverseList(mid):最多遍历一半链表,O(n)

  • 遍历比较两个链表:最多也是 O(n)

总时间复杂度:O(n)


四、空间复杂度分析

  • 所有操作都是原地进行的,没有使用额外的数据结构(如栈或数组)

  • 使用的变量为常数个指针

总空间复杂度:O(1)(常数空间)


✅ 总结一句话

该算法巧妙地结合了快慢指针、链表反转和双指针比较,在线性时间和常数空间下高效判断链表是否为回文。核心在于利用快慢指针找到中点 + 原地反转链表来规避额外空间使用。

我的疑问与解答:

这段代码中为什么不能使用直接 反转链表然后判断是否原链表和反转后的相同,这样的判断方式不可行吗

❓问题本质:

你提到的思路是:

“直接把整个链表反转一遍,然后和原链表一一比较是否相等”。

听起来直观、简单,但在链表这种数据结构下,这么做实际上是不可行的,除非我们付出额外代价。我们分析一下为啥。


🚫 为什么不能直接这么做?

❌ 原链表一旦反转,结构就被破坏了

链表是单向的引用结构,一旦你改变 next 指针,原来的链表结构就消失了。例如:

原链表: A -> B -> C 反转后: C -> B -> A

反转后,你再也无法回到原链表,两个链表共享节点,无法保留两个版本


❌ 比较“原链表”和“反转链表”时无法共存

不像数组可以直接复制出一个副本(空间 O(n)),链表如果你想保留“原链表”+“反转链表”,只能:

  • 额外复制整个链表结构(用 O(n) 空间);

  • 或者做深拷贝 + 比较。

那样时间和空间复杂度都增加了,失去了当前算法 O(1) 空间的优势。


✅ 现在这段代码做了什么优化?

✅ 它没有修改前半部分链表

只从链表中点开始反转,因此:

  • 前半部分结构保留了;

  • 后半部分结构反转后,可以直接比较;

  • 全过程不需要额外空间。


🔁 可选做法(进阶)

✅ 如果你真的想“保留链表结构不变”,可以在比较完后再反转一次恢复结构。

这是一些面试官会追问的点:“你破坏了链表结构,要不要恢复?”

你可以在最后加一句:

self.reverseList(head2) # 恢复后半部分结构(非必须,取决于题意)

✅ 结论总结:

方式是否可行空间复杂度是否破坏链表结构是否高效
整体反转 + 比较原链表❌ 不可行O(n) or 破坏原链表会破坏结构
复制一份 + 反转副本 + 比较✅ 可行O(n)不破坏
当前解法(快慢指针+反转后半段)✅ 优秀解O(1)前半结构保留是 ✔️
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值