目录
[1. 边界处理](#1. 边界处理)
[2. 快慢指针找中点](#2. 快慢指针找中点)
[3. 反转后半部分链表](#3. 反转后半部分链表)
[4. 比较前后两部分](#4. 比较前后两部分)
题目
简单
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:

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

输入:head = [1,2]
输出:false
提示:
- 链表中节点数目在范围
[1, 105]内 0 <= Node.val <= 9
进阶: 你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
算法思路
核心思想:反转后半部分链表进行比较
主要步骤:
-
找到中间节点(使用快慢指针)
-
慢指针每次走一步,快指针每次走两步
-
当快指针到达末尾时,慢指针在中间位置
-
-
反转后半部分链表
- 从中间节点开始反转链表后半部分
-
比较前后两部分
-
分别从头节点和反转后的后半部分头节点开始比较
-
逐个节点比较值是否相等
-
代码
Go
package main
import "fmt"
// 链表节点定义
type ListNode struct {
Val int
Next *ListNode
}
// 解法:反转后半部分链表(O(1)空间复杂度)
func isPalindrome(head *ListNode) bool {
if head == nil || head.Next == nil {
return true
}
// 1. 快慢指针找中点
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
// 2. 反转后半部分
var prev *ListNode
cur := slow
for cur != nil {
next := cur.Next
cur.Next = prev
prev = cur
cur = next
}
// 3. 比较前后两部分
p1, p2 := head, prev
for p2 != nil {
if p1.Val != p2.Val {
return false
}
p1 = p1.Next
p2 = p2.Next
}
return true
}
// 创建测试链表
func createList(nums []int) *ListNode {
if len(nums) == 0 {
return nil
}
head := &ListNode{Val: nums[0]}
cur := head
for i := 1; i < len(nums); i++ {
cur.Next = &ListNode{Val: nums[i]}
cur = cur.Next
}
return head
}
// 测试函数
func main() {
// 测试用例
tests := []struct {
nums []int
expected bool
}{
{[]int{1, 2, 2, 1}, true}, // 示例1
{[]int{1, 2}, false}, // 示例2
{[]int{1}, true}, // 单节点
{[]int{}, true}, // 空链表
{[]int{1, 2, 3, 2, 1}, true}, // 奇数长度回文
{[]int{1, 2, 3, 4}, false}, // 非回文
{[]int{1, 1}, true}, // 两节点相同
{[]int{1, 2, 1}, true}, // 三节点回文
}
// 运行测试
for i, test := range tests {
head := createList(test.nums)
result := isPalindrome(head)
if result == test.expected {
fmt.Printf("测试用例 %d ✓: %v -> %v\n", i+1, test.nums, result)
} else {
fmt.Printf("测试用例 %d ✗: %v -> %v (期望 %v)\n", i+1, test.nums, result, test.expected)
}
}
}
Go
func isPalindrome(head *ListNode) bool {
if head == nil || head.Next == nil {
return true
}
// 快慢指针找中点
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
// 反转后半部分
var prev *ListNode
cur := slow
for cur != nil {
next := cur.Next
cur.Next = prev
prev = cur
cur = next
}
// 比较前后两部分
p1, p2 := head, prev
for p2 != nil {
if p1.Val != p2.Val {
return false
}
p1 = p1.Next
p2 = p2.Next
}
return true
}
代码详解
1. 边界处理
go
if head == nil || head.Next == nil {
return true
}
-
head == nil:空链表,没有节点,视为回文 -
head.Next == nil:只有一个节点,如[1],也是回文 -
这两种情况直接返回
true
2. 快慢指针找中点
go
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
快慢指针原理:
-
slow慢指针:每次走 1 步 -
fast快指针:每次走 2 步 -
当
fast到达末尾时,slow正好在中间
示例说明:
text
链表: 1 -> 2 -> 3 -> 2 -> 1
初始: slow=1, fast=1
第1次: slow=2, fast=3
第2次: slow=3, fast=1
结束: slow指向3(中间节点)
链表: 1 -> 2 -> 2 -> 1
初始: slow=1, fast=1
第1次: slow=2, fast=2
第2次: slow=2, fast=nil
结束: slow指向第二个2
3. 反转后半部分链表
go
var prev *ListNode
cur := slow
for cur != nil {
next := cur.Next
cur.Next = prev
prev = cur
cur = next
}
反转过程详解:
text
假设链表后半部分: 3 -> 2 -> 1 -> nil
cur = 3, prev = nil
第1次循环:
next = cur.Next = 2 // 保存下一个节点
cur.Next = prev = nil // 3 -> nil
prev = cur = 3 // prev指向3
cur = next = 2 // cur指向2
第2次循环:
next = cur.Next = 1 // 保存下一个节点
cur.Next = prev = 3 // 2 -> 3
prev = cur = 2 // prev指向2
cur = next = 1 // cur指向1
第3次循环:
next = cur.Next = nil // 保存下一个节点
cur.Next = prev = 2 // 1 -> 2
prev = cur = 1 // prev指向1
cur = next = nil // cur指向nil
结果: 1 -> 2 -> 3 -> nil
4. 比较前后两部分
go
p1, p2 := head, prev
for p2 != nil {
if p1.Val != p2.Val {
return false
}
p1 = p1.Next
p2 = p2.Next
}
比较逻辑:
-
p1从头节点开始(前半部分) -
p2从反转后的后半部分头节点开始 -
逐个比较节点的值,只要有一个不相等就返回
false
为什么只比较到 p2 != nil?
-
因为后半部分长度 ≤ 前半部分
-
奇数长度时,中间节点不需要比较
-
偶数长度时,两部分长度相等