【每日算法】LeetCode 234. 回文链表详解

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 234. 回文链表

1. 题目描述

给定一个单链表的头节点 head,请判断该链表是否为回文链表。如果是,返回 true;否则,返回 false

示例 1:

复制代码
输入: head = [1,2,2,1]
输出: true

示例 2:

复制代码
输入: head = [1,2]
输出: false

进阶要求: 尝试使用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题。

2. 问题分析

回文链表是指链表节点值从前往后读和从后往前读完全一致。作为前端开发者,我们常处理类似 DOM 树或组件状态树的结构,链表作为一种线性数据结构,在内存管理和优化中具有参考价值。

核心挑战:

  • 链表单向遍历,无法直接反向访问。
  • 需要在有限空间内高效比较节点值。
  • 进阶要求 O(1) 空间,排除使用额外数组或栈等线性空间。

前端关联场景: 例如,在虚拟 DOM 差异算法或状态历史管理中,检查结构对称性可优化渲染性能。

3. 解题思路

3.1 思路一:转换为数组法

将链表值复制到数组,再用双指针从两端向中间比较回文。

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)
  • 优点: 简单直观,易于实现。
  • 缺点: 额外 O(n) 空间,不满足进阶要求。

3.2 思路二:递归法

利用递归栈隐式存储节点,从链表两端向内比较。

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)(递归调用栈)
  • 优点: 代码简洁,体现递归思想。
  • 缺点: 栈空间 O(n),可能栈溢出,不适合长链表。

3.3 思路三:快慢指针反转后半部分法(最优解)

使用快慢指针找到链表中点,反转后半部分链表,再比较前后两半是否一致。最后可选恢复链表。

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
  • 优点: 满足进阶要求,时间 O(n)、空间 O(1)。
  • 缺点: 修改链表结构,但可恢复。

4. 各思路代码实现

4.1 思路一:转换为数组法

javascript 复制代码
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
function isPalindrome(head) {
    const arr = [];
    let curr = head;
    while (curr !== null) {
        arr.push(curr.val);
        curr = curr.next;
    }
    let left = 0, right = arr.length - 1;
    while (left < right) {
        if (arr[left] !== arr[right]) return false;
        left++;
        right--;
    }
    return true;
}

4.2 思路二:递归法

javascript 复制代码
function isPalindrome(head) {
    let frontPointer = head;
    
    function recursivelyCheck(currentNode) {
        if (currentNode !== null) {
            if (!recursivelyCheck(currentNode.next)) return false;
            if (currentNode.val !== frontPointer.val) return false;
            frontPointer = frontPointer.next;
        }
        return true;
    }
    
    return recursivelyCheck(head);
}

4.3 思路三:快慢指针反转后半部分法

javascript 复制代码
function isPalindrome(head) {
    if (head === null || head.next === null) return true;
    
    // 快慢指针找中点
    let slow = head, fast = head;
    while (fast.next !== null && fast.next.next !== null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    
    // 反转后半部分链表
    let secondHalfStart = reverseList(slow.next);
    
    // 比较前后两半
    let p1 = head, p2 = secondHalfStart;
    let isPal = true;
    while (p2 !== null) {
        if (p1.val !== p2.val) {
            isPal = false;
            break;
        }
        p1 = p1.next;
        p2 = p2.next;
    }
    
    // 恢复链表(可选,保持原结构)
    slow.next = reverseList(secondHalfStart);
    
    return isPal;
}

// 辅助函数:反转链表
function reverseList(head) {
    let prev = null, curr = head;
    while (curr !== null) {
        const nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}

5. 各实现思路的复杂度、优缺点对比表格

思路 时间复杂度 空间复杂度 优点 缺点 适用场景
转换为数组法 O(n) O(n) 实现简单,快速原型开发 额外 O(n) 空间,不满足进阶要求 小规模数据或无需空间优化时
递归法 O(n) O(n) 代码简洁,递归思维训练 递归栈 O(n),可能栈溢出,性能较差 学习递归,链表长度有限时
快慢指针反转法 O(n) O(1) 最优解,空间高效,满足进阶要求 需要修改链表(可恢复),实现稍复杂 大规模数据、内存敏感场景

6. 总结

回文链表问题不仅是算法练习,更是前端开发者深化数据结构理解的契机。通过比较不同解法,我们学会在时间与空间之间权衡,这对前端性能优化至关重要。

实际应用场景:

  • 前端状态管理: 如 Redux 或 MobX 中,检查状态变更历史是否对称,以支持撤销/重做功能。
  • 虚拟 DOM 优化: 在 React 等框架中,比较组件树结构是否回文,可减少不必要的渲染。
  • 数据验证: 处理用户输入(如链表形式的嵌套配置)时,验证其对称性。
  • 内存敏感应用: 移动端或低端设备中,O(1) 空间算法能降低内存开销,提升应用流畅度。

作为前端开发者,掌握此类算法将助力你从实现功能转向设计高效系统,提升代码质量和问题解决能力。坚持每日算法练习,结合前端实践,你将在技术道路上走得更远。

相关推荐
千金裘换酒17 小时前
LeetCode 移动零元素 快慢指针
算法·leetcode·职场和发展
wm104317 小时前
机器学习第二讲 KNN算法
人工智能·算法·机器学习
NAGNIP17 小时前
一文搞懂机器学习线性代数基础知识!
算法
NAGNIP18 小时前
机器学习入门概述一览
算法
iuu_star18 小时前
C语言数据结构-顺序查找、折半查找
c语言·数据结构·算法
Yzzz-F18 小时前
P1558 色板游戏 [线段树 + 二进制状态压缩 + 懒标记区间重置]
算法
漫随流水18 小时前
leetcode算法(515.在每个树行中找最大值)
数据结构·算法·leetcode·二叉树
mit6.82419 小时前
dfs|前后缀分解
算法
扫地的小何尚19 小时前
NVIDIA RTX PC开源AI工具升级:加速LLM和扩散模型的性能革命
人工智能·python·算法·开源·nvidia·1024程序员节
千金裘换酒20 小时前
LeetCode反转链表
算法·leetcode·链表