LeetCode:234. Palindrome Linked List
判断一个单链表是否为回文结构,是一道非常经典的链表题。
看似简单,但实际上可以写出 多种解法,并且每种方法都代表了一种典型的解题思想。
本文将系统梳理 五种常见解法,并对它们的时间复杂度、空间复杂度和适用场景进行对比。
一、什么是回文链表?
回文结构指的是:
从前往后读,与从后往前读,结果完全一致
示例:
1 → 2 → 2 → 1 ✅
1 → 2 ❌
链表的难点在于:
不能像数组一样随机访问,只能单向遍历。
二、解法一:复制链表并逆序(最直观)
思路
- 遍历原链表
- 构造一个新的“逆序链表”
- 同时遍历两个链表进行比较
代码
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode r = null;
ListNode p = head;
while (p != null) {
ListNode q = new ListNode(p.val, null);
q.next = r;
r = q;
p = p.next;
}
while (head != null) {
if (head.val != r.val) {
return false;
}
head = head.next;
r = r.next;
}
return true;
}
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
特点
- 思路简单
- 额外开辟链表,空间开销较大
三、解法二:转为数组 + 双指针
思路
- 将链表的值存入
ArrayList - 使用左右双指针向中间逼近
- 逐一比较
代码
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ArrayList<Integer> list = new ArrayList<>();
while (head != null) {
list.add(head.val);
head = head.next;
}
int l = 0, r = list.size() - 1;
while (l < r) {
if (!list.get(l).equals(list.get(r))) {
return false;
}
l++;
r--;
}
return true;
}
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
特点
- 利用数组的随机访问特性
- 写法清晰,容易理解
- 本质还是“空间换时间”
四、解法三:递归(对称遍历)
思路
递归天然具有“后序遍历”的特性:
- 递归到底 → 从尾部开始回溯
- 使用一个全局指针从头部同步向后移动
- 实现“首尾对称比较”
代码
class Solution {
ListNode p;
public boolean isPalindrome(ListNode head) {
p = head;
return recursion(head);
}
private boolean recursion(ListNode head) {
if (head == null) {
return true;
}
if (!recursion(head.next)) {
return false;
}
if (p.val != head.val) {
return false;
}
p = p.next;
return true;
}
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)(递归栈)
特点
- 思路巧妙
- 对递归理解要求高
- 深链表可能导致栈溢出
五、解法四:栈(后进先出)
思路
- 遍历链表,将元素压入栈
- 再次遍历链表,与栈顶元素对比
栈的 LIFO 特性,天然适合回文问题。
代码
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
Stack<Integer> stack = new Stack<>();
ListNode temp = head;
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
}
while (head != null) {
if (head.val != stack.pop()) {
return false;
}
head = head.next;
}
return true;
}
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
特点
- 思路直观
- 使用了额外数据结构
六、解法五:快慢指针 + 反转后半链表(最优解)
核心思想
这是面试最推荐、空间最优的解法:
- 使用快慢指针找到链表中点
- 反转后半部分链表
- 前后两部分逐一比较
代码
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
if (fast != null) {
slow = slow.next;
}
ListNode second = reverseList(slow);
ListNode first = head;
while (second != null) {
if (first.val != second.val) {
return false;
}
first = first.next;
second = second.next;
}
return true;
}
private ListNode reverseList(ListNode head) {
ListNode r = null;
while (head != null) {
ListNode next = head.next;
head.next = r;
r = head;
head = next;
}
return r;
}
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
为什么这是最优解?
- 不依赖额外数据结构
- 完全利用链表特性
- 体现了对指针操作的深刻理解
七、五种方法对比总结
| 方法 | 时间复杂度 | 空间复杂度 | 特点 |
|---|---|---|---|
| 复制逆序链表 | O(n) | O(n) | 简单直观 |
| 数组 + 双指针 | O(n) | O(n) | 易理解 |
| 递归 | O(n) | O(n) | 思路巧妙 |
| 栈 | O(n) | O(n) | 利用 LIFO |
| 快慢指针 | O(n) | O(1) | 最优解 |
八、总结
回文链表这道题的价值,不在于“能不能做出来”,
而在于:
- 是否能想到多种解法
- 是否能权衡空间与时间
- 是否理解链表指针的本质
如果只能写出前几种,说明你“会做题”;
如果能写出 快慢指针 + 原地反转,说明你已经真正掌握了链表。