题目
题目来源:力扣(LeetCode)
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
思路分析
双指针法
首先,遍历链表将节点值存储到数组列表中。我们使用 currentNode 指向当前节点,每次迭代时向数组添加 currentNode.val,并更新 currentNode = currentNode.next ,当 currentNode.next 为 null 时链表循环结束。
然后定义两个指针,prev 指针指向数组列表的起点位置,last 指针指向数组列表的结尾位置,遍历数组,每次迭代时判断两个指向指向的元素是否相同,若不同,则返回 false;若相同,则将两个指针同时向内移动,并继续判断,直到两个指针相遇。
/*** Definition for singly-linked list.* function ListNode(val, next) {* this.val = (val===undefined ? 0 : val)* this.next = (next===undefined ? null : next)* }*//*** @param {ListNode} head* @return {boolean}*/var isPalindrome = function (head) {const vals = [];// 遍历链表,将链表的节点值存储到数组列表中while (head !== null) {vals.push(head.val);head = head.next;}// 定义两个指针,分别指向数组列表的起点位置和结尾位置// 每一次迭代判断两个指针指向的元素是否相同// 若不同,返回 false;相同则将两个指针向内移动,并继续判断,直到两个指针相遇for (let prev = 0, last = vals.length - 1; prev < last; ++prev, --last) {if (vals[prev] !== vals[last]) {return false;}}return true;};
递归
使用递归反向迭代节点,同时使用递归函数外的变量向前迭代,就可以判断链表是否为回文。
首先递归链表节点,直到指针 currentNode 指向尾节点,由于递归的特性,会再从后往前进行比较。frontPointer 是递归函数外的指针。若 currentNode.val != frontPointer.val 则返回 false。反之,frontPointer 向前移动并返回 true。
let frontPointer;const recursivelyCheck = (currentNode) => {if (currentNode !== null) {if (!recursivelyCheck(currentNode.next)) {return false;}if (currentNode.val !== frontPointer.val) {return false;}frontPointer = frontPointer.next;}return true;}var isPalindrome = function(head) {frontPointer = head;return recursivelyCheck(head);};
快慢指针 + 反转后半部分链表
我们可以通过快慢指针找到链表的中间节点,然后以中间节点将链表分为前后两个部分,将后半部分链表反转,然后比较前半部分链表和后半部分链表,比较完成后将链表恢复原样。
具体步骤:
- 通过快慢指针找到链表的中间节点
- 以中间节点将链表分割为前后两部分链表
- 反转后半部分链表
- 比较前后两部分链表,判断是否回文
- 恢复链表
- 返回结果
在步骤1 中,使用快慢指针在一次遍历中找到链表的中间,快指针每次走两步,慢指针每次走一步,快慢指针同时出发,当快指针走到链表的末尾时,慢指针恰好走到链表的中间。我们以慢指针所指向的节点作为分割点,将链表分割为前后两个部分。
步骤2 中以链表中间节点分割链表时,由于是以中间节点作为分割点,若链表有奇数个节点,那么中间的节点应该开作是前半部分链表的节点。
分割完链表后,我们需要将后部分链表反转。
在步骤4 中,按照后半部分链表的长度,比较前后两部分链表的节点值是否相等,当后半部分链表到达末尾时,比较完成,可以忽略前半部分链表的尾节点,即原链表的中间节点。
比较完成后,需要将后半部分链表进行恢复,我们通常不希望链表结构被更改。

代码:
/*** Definition for singly-linked list.* function ListNode(val, next) {* this.val = (val===undefined ? 0 : val)* this.next = (next===undefined ? null : next)* }*//*** @param {ListNode} head* @return {boolean}*/// 反转链表const reverseList = (head) => {let prev = null;let curr = head;while (curr !== null) {let nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return prev;}// 找到前半部分链表的尾节点const endOfFirstHalf = (head) => {let fast = head; // 慢指针let slow = head; // 快指针// 快指针走两步,慢指针走一步,当快指针走到链表的末尾时,慢指针恰好到达链表的中间,然后通过慢指针将链表分为两部分while (fast.next !== null && fast.next.next !== null) {fast = fast.next.next;slow = slow.next;}return slow;}var isPalindrome = function(head) {if (head == null) return true;// 找到前半部分链表的尾节点const firstHalfEnd = endOfFirstHalf(head);// 反转后半部分链表const secondHalfStart = reverseList(firstHalfEnd.next);// 判断是否回文// 前半部分链表头节点let p1 = head;// 后半部分链表头节点let p2 = secondHalfStart;// result 默认为 truelet result = true;// 原链表总长度如果是奇数,那么前半部分链表比后半部分链表多一个节点// 按照后半部分链表的长度,比较前半部分链表和后半部分链表的节点值是否相等while (result && p2 != null) {// 前半部分的链表的节点值与后半部分链表的节点值不相等,说明不是回文链表,返回 falseif (p1.val != p2.val) result = false;p1 = p1.next;p2 = p2.next;}// 还原链表并返回结果firstHalfEnd.next = reverseList(secondHalfStart);return result;};
参考阅读 https://leetcode-cn.com/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode-solution/ https://leetcode-cn.com/problems/palindrome-linked-list/solution/dai-ma-sui-xiang-lu-234-hui-wen-lian-bia-qs0k/
