Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

234. Palindrome Linked List - Leetcode Solution

Code Implementation

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        # Find the end of first half and reverse second half
        def end_of_first_half(node):
            fast = slow = node
            while fast.next and fast.next.next:
                fast = fast.next.next
                slow = slow.next
            return slow

        def reverse(node):
            prev = None
            while node:
                next_node = node.next
                node.next = prev
                prev = node
                node = next_node
            return prev

        if not head or not head.next:
            return True

        first_half_end = end_of_first_half(head)
        second_half_start = reverse(first_half_end.next)

        # Compare both halves
        p1, p2 = head, second_half_start
        result = True
        while p2:
            if p1.val != p2.val:
                result = False
                break
            p1 = p1.next
            p2 = p2.next

        # Restore the list (optional)
        first_half_end.next = reverse(second_half_start)
        return result
      
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (!head || !head->next) return true;

        // Find the end of the first half
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast->next && fast->next->next) {
            slow = slow->next;
            fast = fast->next->next;
        }

        // Reverse the second half
        ListNode* prev = nullptr;
        ListNode* curr = slow->next;
        while (curr) {
            ListNode* next_temp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next_temp;
        }

        // Check palindrome
        ListNode* p1 = head;
        ListNode* p2 = prev;
        bool result = true;
        while (p2) {
            if (p1->val != p2->val) {
                result = false;
                break;
            }
            p1 = p1->next;
            p2 = p2->next;
        }

        // Optional: Restore the list
        curr = prev;
        prev = nullptr;
        while (curr) {
            ListNode* next_temp = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next_temp;
        }
        slow->next = prev;

        return result;
    }
};
      
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) return true;

        // Find the end of first half
        ListNode slow = head, fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        // Reverse second half
        ListNode prev = null, curr = slow.next;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }

        // Compare halves
        ListNode p1 = head, p2 = prev;
        boolean result = true;
        while (p2 != null) {
            if (p1.val != p2.val) {
                result = false;
                break;
            }
            p1 = p1.next;
            p2 = p2.next;
        }

        // Optional: Restore the list
        curr = prev;
        prev = null;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        slow.next = prev;

        return result;
    }
}
      
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function(head) {
    if (!head || !head.next) return true;

    // Find end of first half
    let slow = head, fast = head;
    while (fast.next && fast.next.next) {
        slow = slow.next;
        fast = fast.next.next;
    }

    // Reverse second half
    let prev = null, curr = slow.next;
    while (curr) {
        let nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }

    // Compare halves
    let p1 = head, p2 = prev;
    let result = true;
    while (p2) {
        if (p1.val !== p2.val) {
            result = false;
            break;
        }
        p1 = p1.next;
        p2 = p2.next;
    }

    // Optional: Restore list
    curr = prev;
    prev = null;
    while (curr) {
        let nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    slow.next = prev;

    return result;
};
      

Problem Description

You are given the head of a singly linked list. Your task is to determine whether the values of the nodes in the linked list form a palindrome. A palindrome is a sequence that reads the same backward as forward.

Constraints:

  • The linked list consists of nodes, each containing an integer value.
  • You must check if the sequence of values is the same when traversed from start to end and from end to start.
  • You should not modify the node values, but you may alter node pointers if needed (restoring them at the end is optional).
  • The solution should aim for O(n) time and O(1) space complexity.

Thought Process

At first glance, it might seem easiest to convert the linked list into an array, then check if the array is a palindrome. This would require O(n) extra space, which may not be allowed for large lists or if an in-place solution is desired.

Next, we consider whether we can compare nodes directly in the linked list. Since it's singly linked, we cannot traverse backward easily. This leads us to think about strategies that allow us to compare the first and last halves of the list without extra space.

The key insight is to reverse the second half of the list, so we can compare the first half and the reversed second half node by node. After the comparison, we can restore the original list structure if needed.

Solution Approach

  • Step 1: Find the Middle of the List
    • Use two pointers, slow and fast. Move slow one step at a time and fast two steps at a time.
    • When fast reaches the end, slow will be at the midpoint.
  • Step 2: Reverse the Second Half
    • Starting from slow.next, reverse the second half of the list in-place.
    • This allows us to traverse the second half backward using next pointers.
  • Step 3: Compare Both Halves
    • Set two pointers: one at the head and one at the start of the reversed second half.
    • Compare values node by node. If any mismatch is found, the list is not a palindrome.
  • Step 4: Restore the List (Optional)
    • Reverse the second half again to restore the original list structure, if required.
  • Step 5: Return the Result
    • If all compared nodes matched, return true; otherwise, return false.

This approach ensures O(n) time complexity (since we traverse the list a constant number of times) and O(1) extra space (since we only use a few pointers).

Example Walkthrough

Let's consider the linked list: 1 -> 2 -> 2 -> 1

  1. Find the middle:
    • slow moves to the first 2, fast moves to the last 2.
    • After one more iteration, fast reaches the end, slow is at the first 2 (the midpoint).
  2. Reverse the second half:
    • Reverse nodes starting from the second 2 to 1.
    • The list now looks like 1 -> 2 -> 1 -> 2 (with the second half reversed).
  3. Compare both halves:
    • Compare 1 (head) with 1 (start of reversed half) - match.
    • Compare 2 with 2 - match.
  4. Restore the list (optional):
    • Reverse the second half again to get the original order.
  5. Result:
    • All nodes matched, so the list is a palindrome.

Time and Space Complexity

  • Brute-force approach (copy to array):
    • Time: O(n) to traverse the list and O(n) to check the array for palindrome (total O(n)).
    • Space: O(n) for the array.
  • Optimized approach (reverse second half):
    • Time: O(n) — traverses the list to find the middle, reverse, compare, and restore.
    • Space: O(1) — only a few pointers are used regardless of list size.

The optimized approach is preferred for large lists and when extra space is restricted.

Summary

To determine if a singly linked list is a palindrome, we can efficiently find the midpoint, reverse the second half of the list, and compare both halves node by node. This approach leverages pointer manipulation to avoid extra space, making it both time and space efficient. Restoring the original list is optional but good practice. The elegance of this solution lies in its use of the fast and slow pointer technique and in-place reversal to achieve O(n) time and O(1) space complexity.