回文链表的五种解法全解析(从暴力到最优)

6 阅读3分钟

LeetCode:234. Palindrome Linked List

判断一个单链表是否为回文结构,是一道非常经典的链表题。
看似简单,但实际上可以写出 多种解法,并且每种方法都代表了一种典型的解题思想。

本文将系统梳理 五种常见解法,并对它们的时间复杂度、空间复杂度和适用场景进行对比。


一、什么是回文链表?

回文结构指的是:

从前往后读,与从后往前读,结果完全一致

示例:

1 → 2 → 2 → 1   ✅
1 → 2           ❌

链表的难点在于:
不能像数组一样随机访问,只能单向遍历


二、解法一:复制链表并逆序(最直观)

思路

  1. 遍历原链表
  2. 构造一个新的“逆序链表”
  3. 同时遍历两个链表进行比较

代码

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)

特点

  • 思路简单
  • 额外开辟链表,空间开销较大

三、解法二:转为数组 + 双指针

思路

  1. 将链表的值存入 ArrayList
  2. 使用左右双指针向中间逼近
  3. 逐一比较

代码

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)(递归栈)

特点

  • 思路巧妙
  • 对递归理解要求高
  • 深链表可能导致栈溢出

五、解法四:栈(后进先出)

思路

  1. 遍历链表,将元素压入栈
  2. 再次遍历链表,与栈顶元素对比

栈的 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)

特点

  • 思路直观
  • 使用了额外数据结构

六、解法五:快慢指针 + 反转后半链表(最优解)

核心思想

这是面试最推荐空间最优的解法:

  1. 使用快慢指针找到链表中点
  2. 反转后半部分链表
  3. 前后两部分逐一比较

代码

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)最优解

八、总结

回文链表这道题的价值,不在于“能不能做出来”,
而在于:

  • 是否能想到多种解法
  • 是否能权衡空间与时间
  • 是否理解链表指针的本质

如果只能写出前几种,说明你“会做题”;
如果能写出 快慢指针 + 原地反转,说明你已经真正掌握了链表。