Check Palindrome In Linked List
#include <bits/stdc++.h> // Includes all standard C++ libraries (e.g., iostream, vector, etc.)using namespace std; // Uses the standard namespace
// Node class for a singly linked listclass Node {public: int data; // Data stored in the node Node* next; // Pointer to the next node
// Constructor to create a new node Node(int data) { this->data = data; // Initialize data this->next = NULL; // Initialize next pointer to NULL }};
// Function to print the linked listvoid printList(Node* head) { if(head == NULL) { // Check if list is empty cout << "List is Empty!" << endl; return; }
cout << "Singly List : "; while(head != NULL) { // Traverse and print data cout << head->data << " "; head = head->next; } cout << endl;}
// Function to insert a new node at the head of the listvoid insertAtHead(Node* &head, int data) { Node *temp = new Node(data); // Create new node temp->next = head; // Link new node to current head head = temp; // Update head to new node}
// Function to check if a singly linked list is a palindrome// Approach: Copy list to vector, then check if vector is palindromebool isPalindrome(Node* head) { Node* temp = head; // Temporary pointer for list traversal vector<int> newList; // Vector to store linked list data
// Step 1: Copy linked list data to a vector while(temp != NULL) { newList.push_back(temp->data); // Add current node's data to vector temp = temp->next; // Move to next node }
// Step 2: Check if the vector is a palindrome int start = 0; // Pointer for start of vector int end = newList.size() - 1; // Pointer for end of vector
while(start < end) { // Iterate inwards from both ends if(newList[start] != newList[end]) { // If elements don't match return false; // Not a palindrome } start++; // Move start pointer forward end--; // Move end pointer backward }
return true; // If loop completes, it's a palindrome}
int main() { Node *head = NULL; // Initialize an empty linked list
// Insert elements to create a palindrome list: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 insertAtHead(head, 54); insertAtHead(head, 67); insertAtHead(head, 34); insertAtHead(head, 99); insertAtHead(head, 34); insertAtHead(head, 67); insertAtHead(head, 54);
printList(head); // Print the created list
// Check and print if the list is a palindrome if(isPalindrome(head)) { cout << "List is palindrome!" << endl; } else { cout << "List is not palindrome!" << endl; }
return 0; // Indicate successful execution}
Quick Revision Note: Palindrome Check for Singly Linked List
Problem: Determine if a given singly linked list is a palindrome. A palindrome reads the same forwards and backwards.
Approach Used (isPalindrome
function):
-
Copy to Auxiliary Space (Vector):
- Idea: The simplest way to check for a palindrome in a singly linked list is to copy all its elements into a data structure that allows easy bidirectional access, like a
std::vector
(or an array). - Process: Traverse the linked list from head to tail. For each node, append its
data
to thevector
. - Time Complexity: $O(N)$, where $N$ is the number of nodes, as we visit each node once to copy its data.
- Space Complexity: $O(N)$, as the
vector
will store all $N$ elements of the linked list.
- Idea: The simplest way to check for a palindrome in a singly linked list is to copy all its elements into a data structure that allows easy bidirectional access, like a
-
Two-Pointer Check on Auxiliary Space:
- Idea: Once the elements are in the
vector
, checking for a palindrome becomes straightforward. - Process: Use two pointers,
start
at the beginning of thevector
andend
at the end. Movestart
forwards andend
backwards, comparing elements at each step. If a mismatch is found, it’s not a palindrome. If they meet or cross without mismatches, it is a palindrome. - Time Complexity: $O(N)$, as we iterate through at most half of the
vector
. - Space Complexity: $O(1)$ (ignoring the space used by the
vector
itself, which is part of step 1).
- Idea: Once the elements are in the
Overall Time Complexity: $O(N)$ (dominated by the two passes over the list/vector) Overall Space Complexity: $O(N)$ (due to storing all list elements in the vector)
Example:
Linked List: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 -> NULL
-
Copy to
vector
:newList = [54, 67, 34, 99, 34, 67, 54]
-
Two-Pointer Check:
start = 0
,end = 6
newList[0]
(54) ==newList[6]
(54) -> Match.start = 1
,end = 5
newList[1]
(67) ==newList[5]
(67) -> Match.start = 2
,end = 4
newList[2]
(34) ==newList[4]
(34) -> Match.start = 3
,end = 3
start
is no longer less thanend
. Loop terminates.
Result: true
(List is a palindrome)
Alternative (More Optimal) Approach (Not implemented in the provided code but good for revision):
For a singly linked list, a more space-efficient (but slightly more complex) approach would be:
- Find Middle: Use the “fast and slow pointer” method to find the middle of the linked list.
- Reverse Second Half: Reverse the linked list from the middle to the end.
- Compare Halves: Compare the first half of the original list with the reversed second half.
- Restore List (Optional): Reverse the second half back to restore the original list structure.
This alternative approach has:
- Time Complexity: $O(N)$ (for finding middle, reversing, and comparing).
- Space Complexity: $O(1)$ (does not use auxiliary data structures proportional to N, only a few pointers). This is often preferred in interviews.
#include <bits/stdc++.h> // Includes all standard C++ libraries (e.g., iostream, vector, etc.)using namespace std; // Uses the standard namespace
// Node class for a singly linked listclass Node {public: int data; // Data stored in the node Node* next; // Pointer to the next node
// Constructor to create a new node Node(int data) { this->data = data; // Initialize data this->next = NULL; // Initialize next pointer to NULL }};
// Function to print the linked listvoid printList(Node* head) { if(head == NULL) { // Check if list is empty cout << "List is Empty!" << endl; return; }
cout << "Singly List : "; while(head != NULL) { // Traverse and print data cout << head->data << " "; head = head->next; } cout << endl;}
// Function to insert a new node at the head of the listvoid insertAtHead(Node* &head, int data) { Node *temp = new Node(data); // Create new node temp->next = head; // Link new node to current head head = temp; // Update head to new node}
// Function to check if a singly linked list is a palindrome// Approach: Create a reversed copy of the list and compare it with the original.bool isPalindrome(Node* head) { Node* copy = NULL; // Head of the reversed copy list Node* temp = head; // Temporary pointer for original list traversal
// Step 1: Create a reversed copy of the original linked list // By repeatedly inserting at the head of 'copy', we effectively reverse the order. while(temp != NULL) { insertAtHead(copy, temp->data); // Insert current node's data at head of 'copy' temp = temp->next; // Move to next node in original list }
// Step 2: Compare the original list with its reversed copy // 'head' still points to the original list's head. // 'copy' points to the head of the reversed copy. bool flag = true; // Flag to track if it's a palindrome (assume true initially) temp = copy; // Use 'temp' to traverse the 'copy' list
while(head != NULL) { // Iterate as long as original list has nodes if(head->data != temp->data) { // If data doesn't match flag = false; // Set flag to false break; // No need to continue, it's not a palindrome }
head = head->next; // Move to next node in original list temp = temp->next; // Move to next node in reversed copy }
// Step 3: Deallocate memory used by the 'copy' list // (Important for good memory management, as 'copy' was dynamically allocated) while(copy != NULL) { Node* target = copy; // Pointer to the current node to be deleted copy = copy->next; // Move 'copy' to the next node before deletion delete target; // Deallocate the current node }
return flag; // Return the palindrome check result}
int main() { Node *head = NULL; // Initialize an empty linked list
// Insert elements to create a palindrome list: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 // (Note: insertAtHead adds elements in reverse order of insertion calls) // List built: 54 (last inserted) -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 (first inserted) // So the head will be 54. The actual list in memory is: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 insertAtHead(head, 54); insertAtHead(head, 67); insertAtHead(head, 34); insertAtHead(head, 99); insertAtHead(head, 34); insertAtHead(head, 67); insertAtHead(head, 54);
printList(head); // Print the created list
// Check and print if the list is a palindrome if(isPalindrome(head)) { cout << "List is palindrome!" << endl; } else { cout << "List is not palindrome!" << endl; }
return 0; // Indicate successful execution}
Quick Revision Note: Palindrome Check for Singly Linked List (Using Reversed Copy)
Problem: Determine if a given singly linked list reads the same forwards and backwards (i.e., is a palindrome).
Approach Used (isPalindrome
function):
-
Create a Reversed Copy:
- Idea: Build a completely new linked list by traversing the original list and inserting each node’s data at the head of the new list. Inserting at the head naturally reverses the order of elements.
- Process:
- Initialize
Node* copy = NULL;
- Iterate through the
original list
(usingtemp
pointer). - For each
temp->data
, callinsertAtHead(copy, temp->data)
.
- Initialize
- Time Complexity: $O(N)$, where $N$ is the number of nodes, as we traverse the original list once and perform $N$ head insertions (each $O(1)$).
- Space Complexity: $O(N)$, as we create a new linked list that is a full copy of the original.
-
Compare Original and Reversed Copy:
- Idea: Now that we have the original list (pointed to by
head
) and its reversed version (pointed to bycopy
), we can compare them element by element. - Process:
- Use two pointers, one for
head
and one forcopy
. - Iterate simultaneously, comparing
head->data
andcopy->data
. - If any pair of corresponding elements do not match, the list is not a palindrome.
- If both lists are traversed completely without a mismatch, it is a palindrome.
- Use two pointers, one for
- Time Complexity: $O(N)$, as we traverse both lists once.
- Space Complexity: $O(1)$ (ignoring the space used by the copied list, which is part of step 1).
- Idea: Now that we have the original list (pointed to by
-
Deallocate Copied List:
- Important: Since the
copy
list was dynamically allocated, it’s crucial to deallocate its memory to prevent memory leaks. This is done by traversingcopy
anddelete
ing each node. - Time Complexity: $O(N)$.
- Important: Since the
Overall Time Complexity: $O(N)$ (dominated by the three passes: copy, compare, deallocate). Overall Space Complexity: $O(N)$ (due to creating the full copy of the list).
Example:
Original List: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 -> NULL
-
Create Reversed Copy (
copy
):- Insert
54
(from original head):54 -> NULL
(copy
points to this54
) - Insert
67
:67 -> 54 -> NULL
(copy
points to67
) - Insert
34
:34 -> 67 -> 54 -> NULL
(copy
points to34
) - …and so on.
- Final
copy
list:54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 -> NULL
(which is indeed the reverse)
- Insert
-
Compare Original (
head
) and Reversed Copy (copy
):head->data
(54) vscopy->data
(54) -> Matchhead->data
(67) vscopy->data
(67) -> Matchhead->data
(34) vscopy->data
(34) -> Matchhead->data
(99) vscopy->data
(99) -> Matchhead->data
(34) vscopy->data
(34) -> Matchhead->data
(67) vscopy->data
(67) -> Matchhead->data
(54) vscopy->data
(54) -> Match- Both lists become
NULL
.
Result: true
(List is a palindrome)
Comparison with Vector Approach (Previous isPalindrome
):
- Similarities: Both approaches use $O(N)$ time and $O(N)$ space complexity.
- Differences:
- The
vector
approach uses astd::vector
, which might have slightly better cache performance due to contiguous memory, but involves potential reallocations. - This “reversed copy linked list” approach directly manipulates linked list nodes for the copy, which might be preferred if strictly sticking to linked list operations. Memory deallocation is a manual step.
- The
More Optimal Approach (O(1) Space - for revision):
As mentioned in the previous note, the most optimal solution for space efficiency ($O(1)$ space) involves:
- Finding the middle of the linked list (using fast/slow pointers).
- Reversing the second half of the linked list.
- Comparing the first half with the reversed second half.
- (Optional but good practice) Reversing the second half back to restore the original list. This approach avoids creating a full copy, making it more memory-efficient.
#include <bits/stdc++.h> // Includes all standard C++ libraries (e.g., iostream, vector, etc.)using namespace std; // Uses the standard namespace
// Node class for a singly linked listclass Node {public: int data; // Data stored in the node Node* next; // Pointer to the next node
// Constructor to create a new node Node(int data) { this->data = data; // Initialize data this->next = NULL; // Initialize next pointer to NULL }};
// Function to print the linked listvoid printList(Node* head) { if(head == NULL) { // Check if list is empty cout << "List is Empty!" << endl; return; }
cout << "Singly List : "; while(head != NULL) { // Traverse and print data cout << head->data << " "; head = head->next; } cout << endl;}
// Function to insert a new node at the head of the listvoid insertAtHead(Node* &head, int data) { Node *temp = new Node(data); // Create new node temp->next = head; // Link new node to current head head = temp; // Update head to new node}
// Function to reverse a linked list// Input: head of the list to be reversed// Output: new head of the reversed listNode* reverse(Node* head) { Node* curr = head; // Current node, starts at head Node* prev = NULL; // Previous node, initially NULL Node* next = NULL; // Next node, for temporary storage
while(curr != NULL) { // Iterate until end of list next = curr->next; // Store next node curr->next = prev; // Reverse current node's pointer prev = curr; // Move prev to current node curr = next; // Move current to next node } return prev; // 'prev' will be the new head of the reversed list}
// Function to find the middle of a linked list// Uses the "fast and slow pointer" approachNode* getMiddle(Node* head) { Node* slow = head; // Slow pointer, moves one step at a time Node* fast = head; // Fast pointer, moves two steps at a time
// 'fast' checks for NULL and 'fast->next' checks for odd/even length while(fast != NULL && fast->next != NULL) { fast = fast->next->next; // Fast moves two steps slow = slow->next; // Slow moves one step } return slow; // When fast reaches end, slow is at the middle}
// Function to check if a singly linked list is a palindrome// Optimized approach using O(1) extra space (excluding recursion stack for reverse if recursive)bool isPalindrome(Node* head) { // Edge case: empty or single-node list is a palindrome if (head == NULL || head->next == NULL) { return true; }
Node* middle = getMiddle(head); // Find the middle node of the list Node* headOfSecondHalf = middle->next; // Head of the second half of the list middle->next = NULL; // Break the list into two halves (important for comparison)
headOfSecondHalf = reverse(headOfSecondHalf); // Reverse the second half
Node* head1 = head; // Pointer for the first half Node* head2 = headOfSecondHalf; // Pointer for the reversed second half
bool palindrome = true; // Flag to store the result
// Compare the first half with the reversed second half while(head1 != NULL && head2 != NULL) { // Iterate as long as both halves have elements if(head1->data != head2->data) { // If data doesn't match palindrome = false; // Not a palindrome break; // Exit loop early } head1 = head1->next; // Move to next node in first half head2 = head2->next; // Move to next node in reversed second half }
// Restore the original list (optional but good practice for calling functions) // First, reverse the second half back to its original order headOfSecondHalf = reverse(headOfSecondHalf); // Then, reconnect the two halves middle->next = headOfSecondHalf;
return palindrome; // Return the result}
int main() { Node *head = NULL; // Initialize an empty linked list
// Insert elements to create a palindrome list: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 // List created (head to tail): 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 insertAtHead(head, 54); insertAtHead(head, 67); insertAtHead(head, 34); insertAtHead(head, 99); insertAtHead(head, 34); insertAtHead(head, 67); insertAtHead(head, 54);
printList(head); // Print the created list
// Check and print if the list is a palindrome if(isPalindrome(head)) { cout << "List is palindrome!" << endl; } else { cout << "List is not palindrome!" << endl; }
return 0; // Indicate successful execution}
Quick Revision Note: Palindrome Check for Singly Linked List (Optimized O(1) Space)
Problem: Determine if a singly linked list is a palindrome efficiently, ideally without using extra space proportional to the list size.
Core Idea: A linked list is a palindrome if its first half matches the reverse of its second half.
Approach Used (isPalindrome
function):
-
Find the Middle of the List (
getMiddle
):- Method: Use the “Floyd’s Tortoise and Hare” (fast and slow pointer) algorithm.
slow
pointer moves one step at a time.fast
pointer moves two steps at a time.
- Result: When
fast
reaches the end (orNULL
),slow
will be at the middle node of the list. For even length lists,slow
will be the last node of the first half. For odd length,slow
will be the actual middle node. - Time Complexity: $O(N)$, for a single traversal.
- Space Complexity: $O(1)$.
- Method: Use the “Floyd’s Tortoise and Hare” (fast and slow pointer) algorithm.
-
Reverse the Second Half of the List (
reverse
):- Method:
- Identify the start of the second half:
middle->next
. - Temporarily break the link between the first and second halves:
middle->next = NULL;
. This isolates the first half. - Use a standard iterative linked list reversal algorithm on the second half.
- Identify the start of the second half:
- Result: The second half of the list is now reversed.
- Time Complexity: $O(N/2)$, approximately $O(N)$, for reversing half the list.
- Space Complexity: $O(1)$.
- Method:
-
Compare the First Half with the Reversed Second Half:
- Method:
- Use one pointer (
head1
) starting from the originalhead
. - Use another pointer (
head2
) starting from the head of the reversed second half. - Compare
head1->data
andhead2->data
sequentially.
- Use one pointer (
- Result: If all corresponding data matches until
head2
becomesNULL
, the list is a palindrome. If a mismatch occurs, it’s not. - Time Complexity: $O(N/2)$, approximately $O(N)$.
- Space Complexity: $O(1)$.
- Method:
-
Restore the Original List (Crucial for Function Reusability):
- Method: This step is important if you intend for the original linked list structure to remain unchanged after the
isPalindrome
call.- Reverse the (now compared) second half back to its original order.
- Reconnect the two halves: Set
middle->next
back to the (now re-reversed) head of the second half.
- Time Complexity: $O(N/2)$, approximately $O(N)$.
- Space Complexity: $O(1)$.
- Method: This step is important if you intend for the original linked list structure to remain unchanged after the
Overall Time Complexity: $O(N)$ (dominated by multiple traversals/reversals, each linear). Overall Space Complexity: $O(1)$ (constant extra space, as no auxiliary data structures growing with N are used).
Example:
List: 54 -> 67 -> 34 -> 99 -> 34 -> 67 -> 54 -> NULL
(Length 7)
-
getMiddle(head)
:slow
goes54 -> 67 -> 34 -> 99
fast
goes54 -> 34 -> 34 -> 54 -> NULL
(reaches end)middle
points to99
.headOfSecondHalf
(initiallymiddle->next
) becomes34
.middle->next = NULL
breaks list:54 -> 67 -> 34 -> 99 -> NULL
and34 -> 67 -> 54 -> NULL
.
-
reverse(headOfSecondHalf)
:- The second half
34 -> 67 -> 54 -> NULL
becomes54 -> 67 -> 34 -> NULL
. headOfSecondHalf
now points to54
(the new head of the reversed second half).
- The second half
-
Compare:
head1
(original head54
) vshead2
(reversed second half head54
) -> Match.head1
(67) vshead2
(67) -> Match.head1
(34) vshead2
(34) -> Match.head1
(99) vshead2
(NULL) -> Comparison stops here.head2
isNULL
. The comparisonwhile(head1 != NULL && head2 != NULL)
covers this.- The loop finishes without
palindrome
being set tofalse
.
-
Restore: (Not shown in example trace for brevity, but happens in code)
reverse(headOfSecondHalf)
reverses it back to34 -> 67 -> 54 -> NULL
.middle->next = headOfSecondHalf
reconnects99 -> 34
.
Result: true
(List is a palindrome)
This optimized method is generally preferred in coding interviews due to its superior space complexity.