回文链表
难度:简单
给定一个链表的 头节点 head
,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。
示例 1:
输入: head = [1,2,3,3,2,1]
输出: true
示例 2:
输入: head = [1,2]
输出: false
解法一、将值复制到数组中后用双指针法
思路:
一共为两个步骤:
- 复制链表值到数组列表中。
- 使用双指针法判断是否为回文。
第一步,我们需要遍历链表将值复制到数组列表中。每次迭代向数组添加 head.val
,并更新 head = head.next
,当 head = null
时停止循环。
第二步,使用双指针法来检查是否为回文。我们在起点放置一个指针,在结尾放置一个指针,每一次迭代判断两个指针指向的元素是否相同,若不同,返回 false
;相同则将两个指针向内移动,并继续判断,直到两个指针相遇。
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 指的是链表的元素个数。
空间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
lis = []
while head:
lis.append(head.val)
head = head.next
l, r = 0, len(lis) - 1
while l < r:
if lis[l] != lis[r]:
return False
l += 1
r -= 1
return True
解法二、快慢指针 + 反转链表
思路:
避免使用
O
(
n
)
O(n)
O(n) 额外空间的方法就是改变输入。
我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。
该方法虽然可以将空间复杂度降到 O ( 1 ) O(1) O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。
整个流程可以分为以下四个步骤:
- 找到前半部分链表的尾节点:
我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。 - 反转后半部分链表:
可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。 - 判断是否回文:
比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。 - 返回结果。
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 指的是链表的大小。
空间复杂度:
O
(
1
)
O(1)
O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过
O
(
1
)
O(1)
O(1)。
class Solution:
def reversal(self, root):
prev = None
while root:
temp = root.next
root.next = prev
prev = root
root = temp
return prev
def isPalindrome(self, head: ListNode) -> bool:
s, q = head, head
while q is not None and q.next is not None:
s = s.next
q = q.next.next
if q:
s = s.next
s = self.reversal(s)
while s:
if s.val != head.val:
return False
s = s.next
head = head.next
return True
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/aMhZSa