π‘ Problem Formulation: Determining if a singly linked list is a palindrome involves verifying whether the sequence of data in the nodes reads the same forwards and backwards. For example, given a linked list 1 -> 2 -> 2 -> 1, the desired output would be True as it is symmetrical and thus a palindrome.
Method 1: Using a Stack
This method involves traversing the linked list and pushing all elements onto a stack. By popping elements from the stack and comparing them with the second half of the linked list, we can check for palindrome properties since a stack is a LIFO (Last In, First Out) structure, allowing us to compare elements in reverse.
Here’s an example:
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
def is_palindrome(head):
stack = []
slow = fast = head
while fast and fast.next:
stack.append(slow.val)
slow = slow.next
fast = fast.next.next
if fast:
slow = slow.next
while slow:
if stack.pop() != slow.val:
return False
slow = slow.next
return True
# Example LinkedList: 1 -> 2 -> 2 -> 1
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(2)
node4 = ListNode(1)
node1.next = node2
node2.next = node3
node3.next = node4
print(is_palindrome(node1))
Output: True
This code example defines a linked list and a function is_palindrome() which uses a stack to store the first half of the list’s elements. Then it compares these elements with the second half of the list to determine if the linked list is a palindrome.
Method 2: Reversing the Second Half
Method 2 entails reversing the second half of the linked list and then comparing it with the first half. If both halves are identical, the list is a palindrome. This method minimizes space usage since it does not require extra storage for the list’s values.
Here’s an example:
class ListNode:
# ListNode class definition
# ...
def reverse_list(head):
prev = None
current = head
while current:
next_temp = current.next
current.next = prev
prev = current
current = next_temp
return prev
def is_palindrome(head):
slow = fast = head
# Find the mid of the linked list
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse the second half
slow = reverse_list(slow)
# Compare the two halves
while slow:
if head.val != slow.val:
return False
head = head.next
slow = slow.next
return True
# Reuse the previously given example LinkedList and function calls
Output: True
The code defines a function reverse_list() to reverse the second half of the linked list. In the function is_palindrome(), after finding the middle of the list, the second half is reversed and compared with the first half to check for a palindrome.
Method 3: Recursive Approach
A recursive approach can be used to compare the elements from the start and end simultaneously without altering the structure of the linked list. This method is elegant but could use extra stack space due to recursion, which means it might not be the most space-efficient for very long lists.
Here’s an example:
class ListNode:
# ListNode class definition
# ...
def is_palindrome_recursor(left, right):
if not right:
return True, left
result, left = is_palindrome_recursor(left, right.next)
if not result or left.val != right.val:
return False, left
return True, left.next
def is_palindrome(head):
result, _ = is_palindrome_recursor(head, head)
return result
# Reuse the previously given example LinkedList and function calls
Output: True
In this recursive code snippet, is_palindrome_recursor() function traverses the linked list and on its way back it compares each element from the start and end, returning True if it is a palindrome.
Method 4: Optimized Space Approach
This method involves creating a copy of the linked list’s values and then using the two-pointer technique to compare the original list and the copied list simultaneously from outward in. This provides a balance between readability and space efficiency.
Here’s an example:
class ListNode:
# ListNode class definition
# ...
def is_palindrome(head):
values = []
current = head
while current:
values.append(current.val)
current = current.next
left, right = 0, len(values) - 1
while left < right:
if values[left] != values[right]:
return False
left += 1
right -= 1
return True
# Reuse the previously given example LinkedList and function calls
Output: True
The code extracts the list’s values into an array values and then uses the two-pointer technique to check if the list is a palindrome by iterating through the array from both ends.
Bonus One-Liner Method 5: Pythonic Approach
A one-liner in Python can be derived using list comprehension and the all() function. This is a more Pythonic way to check if the list is a palindrome by comparing the values directly while traversing them only once.
Here’s an example:
class ListNode:
# ListNode class definition
# ...
def is_palindrome(head):
values = [node.val for node in iter(lambda: head if head else None, None)]
return values == values[::-1]
# Reuse the previously given example LinkedList and function calls
Output: True
This method converts the singly linked list into a list using list comprehension and then checks if it reads the same forward and backward by comparing the list to its reversed version.
Summary/Discussion
- Method 1: Using a Stack. Very intuitive. Requires extra space proportional to the list size.
- Method 2: Reversing the Second Half. Space-optimized. Modifies the list during processing which could be an issue if the list data must remain unchanged.
- Method 3: Recursive Approach. Elegant and straightforward. Not space-efficient for large lists due to recursion stack space.
- Method 4: Optimized Space Approach. Balances space efficiency with readability. Involves copying the list into an array.
- Method 5: Pythonic Approach. Simple and clean. Relies on Python’s powerful syntax and built-in functions for a concise solution.
