题目
给你一个单链表的头节点
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)
空间复杂度解决此题?
解题思路
刚拿到题目的时候第一反应是像数组那样处理,但这里的数据类型是链表,不能直接当成数组来处理。但按照这个思路,可以遍历链表,把里面的值逐个加入数组中,再判断数组是不是回文就好了。同理,还可以借助栈来实现。但这种方法需要额外消耗空间,不满足空间复杂度O(1),所以在这里就不展开了。
法一:快慢指针(执行用时:940ms,内存消耗:65.5MB)
要满足时间复杂度O(n)空间复杂度O(1)的,首先想到快慢指针。
这一块核心是双指针+反转,主要利用链表的两个操作:
(1)找到链表的中间节点
(2)反转链表
经过这两个操作,原链表将以中间节点被一分为二,前半部分是一个链表,后半部分(反转后)是一个链表。接着遍历这两个链表,如果遍历过程中发现两个链表的节点不同,说明不是回文链表,直接返回false即可。如果链表遍历完成,说明是回文链表。
需要注意的是链表长度可能是偶数,也可能是奇数。当链表长度是奇数时,后半部分长度将比前半部分多1,因此最后迭代链表时,应以前半部分为准。
注意:快慢指针不断迭代,找中间节点:
快慢指针一开始都指向链表的头节点,然后顺着链表走,快指针每次走两步,慢指针走一步,等快指针到达终止位置时,慢指针刚好走到链表中间位置。
同时用pre来记录慢指针指向节点的前一个指针,用于分割链表。
后半部分的反转可以看下这个动画(LeetCode大佬王尼玛在评论区分享的):
https://pic.leetcode-cn.com/7d8712af4fbb870537607b1dd95d66c248eb178db4319919c32d9304ee85b602-迭代.gif
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def isPalindrome(self, head):
"""
:type head: ListNode
:rtype: bool
"""
# 当链表是空的或者只有一个节点时,都属于回文链表
if not (head and head.next):
return True
#快慢指针迭代,找到链表中间节点,pre记录慢指针前一个节点
slow,fast = head,head
while fast and fast.next:
pre = slow
slow = slow.next
fast = fast.next.next
#分割链表,cur1是前半部分,cur2为反转后的后半部分
pre.next = None
cur1 = head #这里链表已经被分割了,但链表头节点还是head
cur2 = self.reverseList(slow)
#前后两部分进行匹配
while cur1:
if cur1.val != cur2.val:
return False
cur1 = cur1.next
cur2 = cur2.next
return True
#反转后半部分链表
def reverseList(self,slow):
cur = slow
pre = None
while(cur != None):
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
这里给出官方的参考代码,也是快慢指针,但官方的代码比较简洁。(执行用时:656ms,内存消耗:48.2MB)
官方的思路是对快慢指针进行迭代的同时对前半部分进行反转,然后比较两部分内容。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def isPalindrome(self, head):
rev, slow, fast = None, head, head
#快慢指针迭代,同时前半部分rev进行反转
while fast and fast.next:
fast = fast.next.next
rev, rev.next, slow = slow, rev, slow.next
#定位后半部分开始节点
if fast:
slow = slow.next
#比较两部分
while slow:
if slow.val == rev.val:
slow = slow.next
rev = rev.next
else:
return False
return True
时间复杂度:O(n),n为链表长度
空间复杂度:O(1)
法二:递归(执行用时:1704ms,内存消耗:173.3MB)
该方法的时间复杂度和空间复杂度都是O(n),但感觉挺有意思的,也记录一下吧。
因为链表是一种兼顾迭代和递归的数据结构,所以链表可以进行先序遍历和后序遍历(树就是多叉链表)。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def isPalindrome(self, head):
self.left = head
def helper(right=head):
if not right:
return True
res = helper(right.next)
if self.left.val != right.val:
return False
self.left = self.left.next
return res
return helper()
知识补充:
在链表中最基本的 .val 表示值,.next 表示链表的指向(指向下一个)。关于链表的更多知识: