代码改变世界

用go构建-回文链表

2026-01-24 11:13  tlnshuju  阅读(0)  评论(0)    收藏  举报

目录

题目

算法思路

代码

代码详解

1. 边界处理

2. 快慢指针找中点

3. 反转后半部分链表

4. 比较前后两部分


题目

​​​​​234. 回文链表

简单

给你一个单链表的头节点 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) 空间复杂度解决此题?

算法思路

核心思想:反转后半部分链表进行比较

主要步骤:

  1. 找到中间节点(使用快慢指针)

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

    • 当快指针到达末尾时,慢指针在中间位置

  2. 反转后半部分链表

    • 从中间节点开始反转链表后半部分

  3. 比较前后两部分

    • 分别从头节点和反转后的后半部分头节点开始比较

    • 逐个节点比较值是否相等

代码

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)
        }
    }
}
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

  • 因为后半部分长度 ≤ 前半部分

  • 奇数长度时,中间节点不需要比较

  • 偶数长度时,两部分长度相等