题目
给你一个单链表的头节点 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) 空间复杂度解决此题?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindrome-linked-list
解题
1.双指针 时间复杂度O(n)
第一个和倒数第一个比较,以此类推。
var isPalindrome = function (head) {
var reverseHead = [];
while (head != null) {
reverseHead.push(head.val);
head = head.next;
}
for (let i = 0, j = reverseHead.length - 1; i < j; i++, j--) {
if (reverseHead[i] !== reverseHead[j]) {
return false;
}
}
return true;
}
2.快慢指针 空间复杂度O(1)
链表的每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
链表的迭代法反转有三步组成:
1.定义指针 prev 初始值 null,curr 指针头指针 head。
2循环移动指针,判断当前curr指针是否为空,如果为空,则表示到达尾部,则停止循环。
3.当curr为空的时候,表示到了尾部,此时新的链表头部为prev,所以实现了链表的反转。
链表结点的next和下一结点中,插入prev,返回之后prev则为反转链表。 相当于快慢指针是找到链表中点的方法慢指针的长度永远是快指针的一半。
执行步骤:
执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
若链表有奇数个节点,则中间的节点应该看作是前半部分。
步骤二反转链表的后半部分。
步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。
const reverseList = (head) => {
let prev = null;
let curr = head;
while (curr !== null) {
let nextTemp = curr.next; // 由于下一步的 curr.next 指向了 prev,会无法获取到下一个 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;
let result = true;
while (result && p2 != null) {
if (p1.val != p2.val) result = false;
p1 = p1.next;
p2 = p2.next;
}
// 还原链表并返回结果
firstHalfEnd.next = reverseList(secondHalfStart);
return result;
};