234 Palindrome Linked List
follow-up
follow-up, 要求\(O(n)\) in time, \(O(1)\) in space的解,在2021年作为facebook的面试题出现,问最优解.
简约但不简单, 数学的化归思想,可以把这个问题转化问已知的问题, 比如
想到这两点是成功的一半,valid palindrome很容易想到,但是reverse linked list你想到的概率会小不少.
Approach 1 to array
按照wisdom peak的话,这题解法还不够优秀.
complexity
- Time complexity: \(O(n)\)
- Space complexity: \(O(n)\)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if not head:
return False
res = []
curr = head
while curr:
res.append(curr.val)
curr = curr.next
left,right = 0,len(res)-1
while left < right:
if res[left] != res[right]:
return False
left += 1
right -= 1
return True
Approach 2: in-place reverse
这个解法包括了以下几个技巧:
two runner pointerreverse a linked list
Note
in-place algo, 在concurrent setting会lock这个input till the operation on the input is done,这是in-place algo的通病
这一题的具体思路如下:
- write two helper functions:
def reverse(self,head): return reversed linked listdef mid_node(self,head): 找到linked list中点
- reverse the 2nd half of the linked list
- use
two pointersto compare the first half and 2nd half from start to end - restore the linked list by re-reverse the 2nd half
Tip
快慢指针有几个作用,
- 快指针速度是慢指针两倍,可以determine
mid nodeandend node. Generally, 我们可以找到1/3点,1/4点,1/5点等等 with different speed multiplier - cycle detection
Step1: two helper functions
For the def reverse(self,head), refers to 206 reverse a linked list.
For the def mid_node(self,head), 有两种思路:
- traverse with counter, then do floor division to get mid node
- 快慢指针
快慢指针technique设置了两个pointer moving at different pace, 由于我们是要找中点,所以设置的两个pointer, and they are slow and fast pointer, respectively. fast向前advancing的速度比slow快两倍, illustrated in the figure below

It stops until one of the next two nodes after fast is null
注意,由于panlindom linked list都拥有偶数个nodes,所以slow pointer会停在前半段的最后一个.
举个例子,如果你的head = [1,2,3,4] at beginning, 那么slow = [2,3,4]
Step2: reverse the 2nd half of the linked list
由于slow pointer停在前半段最后一个,reverse from slow pointer会造成如图的景象;

如果你的head = [1,2,3,4] at beginning, 那么slow = [2,3,4] ,然后你reverse了slow with rev_half = self.reverse(slow),你的pointer就会变化如下:
head = [1,2]slow = [2]rev_half = [4,3,2]
这种情况node with value 2被两个链表同时占有,同时slow pointer正好指向last non-null node in both linked list.
Step3: compare two linked list
这一步很简单,只需要traverse两个list, 逐个比较就可以了,注意由于前半链表永远比后半链表少一个node, 循环条件要注意

Step4: restore
It's a good practice to restore the input head since head might also be used in other functions.
self.reverse(second_half) 就可以了,因为它会修改second half linked list, 当你用指针head来traverse时,原先的断点已经被修复了

Complexity
- Time complexity: \(O(n)\)
- Space complexity: \(O(1)\)
Code Implementation
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
"""
1. reverse the half of linked list
1 -> 2 -> 2 -> 1
1 -> 2 -> 1 -> 2
i. j
1 -> 2 -> 3 -> 2 -> 1
1.1 finding the middle index
1.2 reverse linked list
2. reverse it!!
3. use two separate pointers on two array to traverse to determine if it's palindrom
4. restore it
"""
def get_mid(head):
end = head
mid = head
while end and end.next:
mid = mid.next
end = end.next.next
return mid
def reverse(head):
if not head:
return None
curr,prev = head,None
while curr:
temp = curr.next
curr.next = prev
prev = curr
curr = temp
return prev
mid = get_mid(head)
mid_head = reverse(mid)
left = head
right = mid_head
flag = True
while left and right:
if left.val != right.val:
flag = not flag
break
left = left.next
right = right.next
# we have to reverse the likedin list back no matter what we return
reverse(mid_head)
return flag