What are Linked Lists?
Linked lists are a fundamental data structure in computer science used to store collections of elements. Unlike arrays, which store elements in contiguous memory locations, linked lists consist of nodes where each node contains a data element and a reference (or link) to the next node in the sequence. This structure allows for flexible memory utilization and dynamic memory allocation.
Why are Linked Lists Used?
Dynamic Size: The size of a linked list can grow or shrink dynamically, so it's more flexible in using memory compared to static data structures like arrays.
Ease of Insertions/Deletions: Adding or removing nodes doesn't require the elements to be contiguous in memory, making these operations potentially more efficient than in arrays, where shifting elements is often necessary.
Advantages of Linked Lists over Arrays
No Memory Wastage: Arrays allocate memory based on a fixed size. If the allocation is too large, memory is wasted. If too small, resizing is necessary, which is costly. Linked lists, however, allocate memory as needed.
Efficient Insertions/Deletions: In a linked list, insertions and deletions can be performed more efficiently because these operations do not require elements to be contiguous in memory.
Disadvantages of Linked Lists compared to Arrays
Memory Overhead: Each node in a linked list requires extra memory for storing the pointer to the next node.
Access Time: Linked lists do not allow direct access to elements by their position. To access an element, you must traverse the list from the beginning, which can be time-consuming.
No Cache Locality: The non-contiguous storage of linked lists can lead to poor cache performance compared to arrays, which can significantly impact performance.
Singly Linked List
Here's a simple representation of a singly linked list with three nodes:
[Node1] [Node2] [Tail]
+----+----+ +----+----+ +----+----+
| 20 | |->| 40 | |-> | 60 | |-> null
+----+----+ +----+----+ +----+----+
Node: Each node contains two parts:
Data: The actual value stored (e.g., 20, 40, 60).
Next: A pointer to the next node in the list.
Tail: The last node of the list which points to null, indicating the end of the list.
Doubly Linked List
A doubly linked list is an extension of the singly linked list, where each node contains an additional link field pointing to the previous node in the sequence. This two-way linkage allows traversal of the list in both directions, making some operations more efficient, particularly deletions and insertions at both ends and in the middle of the list.
Here's a representation of a doubly linked list with three nodes:
[Node1/Head] [Node2] [Node3/Tail]
| | |
V V V
+--------+ +--------+ +--------+
| Next | -------->| Next | -------->| Next | --------> null
null <-------- | Prev | <--------| Prev | <--------| Prev |
+--------+ +--------+ +--------+
| Data | | Data | | Data |
| 20 | | 40 | | 60 |
+--------+ +--------+ +--------+
Nodes (Node1/Head, Node2, Node3/Tail):
Links:
Next Link: This arrow points from the "Next" part of one node to the "Next" part of the following node. It shows the direction to the next node in the list.
Prev Link: This arrow points from the "Prev" part of one node to the "Prev" part of the preceding node, illustrating the connection back to the previous node.
Boundaries:
null Pointers:
The "Prev" pointer of the first node (Node1/Head) points to null, indicating that there is no node before it.
The "Next" pointer of the last node (Node3/Tail) points to null, indicating that there is no node after it.
The doubly linked list allows for easier deletion of a node (as no predecessor is needed), and efficient insertion operations before or after a given node, enhancing versatility compared to singly linked lists.
To transform a singly or doubly linked list into a circular linked list, you need to modify the way the nodes are linked so that the list forms a continuous loop. Here's how you can make these modifications:
Singly Linked Circular List
In a singly linked circular list, the "Next" pointer of the last node is modified to point back to the first node instead of pointing to null. This creates a loop, allowing for continuous traversal of the list.
Modification Steps:
Identify the Tail: Find the last node of the list, which would typically have its "Next" pointer set to null.
Link to Head: Change the "Next" pointer of this last node to point to the head of the list instead of null.
This creates a circular structure where you can start at any node and eventually return to that starting point by continuously following the "Next" pointers.
Doubly Linked Circular List
A doubly linked circular list not only links the last node back to the first node but also links the first node back to the last node. This allows for seamless bidirectional traversal.
Modification Steps:
Identify the Tail and Head: Find the last node (Tail) and the first node (Head) of the list.
Link Tail to Head: Change the "Next" pointer of the Tail to point to the Head.
Link Head to Tail: Change the "Prev" pointer of the Head to point to the Tail.
This modification ensures that the list forms a complete loop, allowing navigation from any node to any other node in both forward and backward directions without ever encountering a null pointer.
Here's a simple visualization of both types:
Singly Linked Circular List:
[Head] ---> [Node2] ---> [Node3] ---> [Tail]
^ |
|______________________________________|
Doubly Linked Circular List:
[Head] <--> [Node2] <--> [Node3] <--> [Tail]
^ ^
|__________________________________________|
Practical Usage
Circular linked lists are particularly useful in applications where the data naturally forms a cycle, such as:
Implementing round-robin scheduling algorithms.
Creating a continuously looping playlist or slideshow.
Managing applications where resources need to be recycled in a fixed, repetitive order.
These modifications allow for easier management of such cyclic data structures, avoiding special cases for the end of the list in many operations, which can simplify coding and reduce error potential.
Singly linked lists are dynamic data structures that consist of nodes linked together by pointers. Here's a breakdown of common operations on singly linked lists, each accompanied by sample C code:
Node Structure Definition
First, define the structure of a node in the linked list:
typedef struct Node {
int data;
struct Node* next;
} Node;
1. Creating a New Node
Function to create a new node with given data:
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Unable to allocate memory for new node\n");
exit(-1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
2. Insertion at the Head
Inserts a new node at the beginning of the list:
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
3. Insertion at the Tail
Inserts a new node at the end of the list:
void insertAtTail(Node** head, int data) {
Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
} else {
Node* current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
}
4. Insertion in the Middle
Inserts a new node after a given node:
void insertAfterNode(Node* prevNode, int data) {
if (prevNode == NULL) {
printf("The given previous node cannot be NULL\n");
return;
}
Node* newNode = createNode(data);
newNode->next = prevNode->next;
prevNode->next = newNode;
}
5. Deletion at the Head
Deletes the first node of the list:
void deleteAtHead(Node** head) {
if (*head == NULL) return;
Node* temp = *head;
*head = (*head)->next;
free(temp);
}
6. Deletion at the Tail
Deletes the last node of the list:
void deleteAtTail(Node** head) {
if (*head == NULL) return;
if ((*head)->next == NULL) {
free(*head);
*head = NULL;
return;
}
Node* current = *head;
while (current->next->next != NULL) {
current = current->next;
}
free(current->next);
current->next = NULL;
}
7. Deletion in the Middle
Deletes a node after a given node:
void deleteAfterNode(Node* prevNode) {
if (prevNode == NULL || prevNode->next == NULL) return;
Node* temp = prevNode->next;
prevNode->next = temp->next;
free(temp);
}
8. Searching for a Node
Searches for a node containing a specific data:
Node* search(Node* head, int key) {
Node* current = head;
while (current != NULL) {
if (current->data == key) {
return current;
}
current = current->next;
}
return NULL;
}
9. Length of List
This function counts the number of nodes in the list:
int listLength(Node* head) {
int count = 0;
Node* current = head;
while (current != NULL) {
count++;
current = current->next;
}
return count;
}
10. Reverse the List
This function reverses the order of nodes in the list:
void reverseList(Node** head) {
Node* prev = NULL;
Node* current = *head;
Node* next = NULL;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
*head = prev;
}
11. Sort the List
This function sorts the list using the simple bubble sort algorithm, which is suitable for educational purposes but not the most efficient for large lists:
void sortList(Node** head) {
int swapped;
Node* ptr1;
Node* lptr = NULL;
if (*head == NULL)
return;
do {
swapped = 0;
ptr1 = *head;
while (ptr1->next != lptr) {
if (ptr1->data > ptr1->next->data) {
int temp = ptr1->data;
ptr1->data = ptr1->next->data;
ptr1->next->data = temp;
swapped = 1;
}
ptr1 = ptr1->next;
}
lptr = ptr1;
} while (swapped);
}
Below are implementations for various common operations on doubly linked lists in C, including node creation, insertion, deletion, searching, and additional operations such as finding the length of the list, reversing the list, and sorting the list. Each function is designed considering the structure where the list starts from the first node directly without a separate head structure.
Node Structure Definition
First, let's define the structure of a node in a doubly linked list:
typedef struct Node {
int data;
struct Node* next;
struct Node* prev;
} Node;
1. Creating a New Node
Function to create a new node with given data:
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (!newNode) {
fprintf(stderr, "Unable to allocate memory for new node\n");
exit(-1);
}
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
2. Insertion at the Head
Inserts a new node at the beginning of the list:
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data);
newNode->next = *head;
if (*head != NULL) {
(*head)->prev = newNode;
}
*head = newNode;
}
3. Insertion at the Tail
Inserts a new node at the end of the list:
void insertAtTail(Node** head, int data) {
Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
Node* tail = *head;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = newNode;
newNode->prev = tail;
}
4. Insertion in the Middle
Inserts a new node after a given node:
void insertAfterNode(Node* prevNode, int data) {
if (!prevNode) {
printf("The given previous node cannot be NULL\n");
return;
}
Node* newNode = createNode(data);
newNode->next = prevNode->next;
newNode->prev = prevNode;
if (prevNode->next != NULL) {
prevNode->next->prev = newNode;
}
prevNode->next = newNode;
}
5. Deletion at the Head
Deletes the first node of the list:
void deleteAtHead(Node** head) {
if (*head == NULL) return;
Node* temp = *head;
*head = (*head)->next;
if (*head != NULL) {
(*head)->prev = NULL;
}
free(temp);
}
6. Deletion at the Tail
Deletes the last node of the list:
void deleteAtTail(Node** head) {
if (*head == NULL) return;
Node* tail = *head;
while (tail->next != NULL) {
tail = tail->next;
}
if (tail->prev != NULL) {
tail->prev->next = NULL;
} else {
*head = NULL;
}
free(tail);
}
7. Deletion in the Middle
Deletes a node after a given node:
void deleteAfterNode(Node* prevNode) {
if (prevNode == NULL || prevNode->next == NULL) return;
Node* temp = prevNode->next;
prevNode->next = temp->next;
if (temp->next != NULL) {
temp->next->prev = prevNode;
}
free(temp);
}
8. Searching for a Node
Searches for a node containing a specific data:
Node* search(Node* head, int key) {
Node* current = head;
while (current != NULL) {
if (current->data == key) {
return current;
}
current = current->next;
}
return NULL;
}
9. Length of List
This function counts the number of nodes in the list:
int listLength(Node* head) {
int count = 0;
Node* current = head;
while (current != NULL) {
count++;
current = current->next;
}
return count;
}
10. Reverse the List
This function reverses the order of nodes in the list:
void reverseList(Node** head) {
Node* temp = NULL;
Node* current = *head;
while (current != NULL) {
temp = current->prev;
current->prev = current->next;
current->next = temp;
current = current->prev;
}
if (temp != NULL) {
*head = temp->prev;
}
}
11. Sort the List
This function sorts the list using a simple bubble sort algorithm:
void sortList(Node** head) {
int swapped;
Node* ptr1;
Node* lptr = NULL;
if (*head == NULL) return;
do {
swapped = 0;
ptr1 = *head;
while (ptr1->next != lptr) {
if (ptr1->data > ptr1->next->data) {
int temp = ptr1->data;
ptr1->data = ptr1->next->data;
ptr1->next->data = temp;
swapped = 1;
}
ptr1 = ptr1->next;
}
lptr = ptr1;
} while (swapped);
}
For circular linked lists, both singly and doubly linked, some operations require slight modifications due to the cyclical nature of the lists. Below, I'll provide C code for searching, sorting, reversing, and finding the length for both singly and doubly circular linked lists.
Singly Circular Linked List
1. Length of List
int circularListLength(Node* head) {
if (head == NULL) return 0;
Node* current = head;
int count = 0;
do {
count++;
current = current->next;
} while (current != head);
return count;
}
2. Search for a Node
Node* circularSearch(Node* head, int key) {
if (head == NULL) return NULL;
Node* current = head;
do {
if (current->data == key) return current;
current = current->next;
} while (current != head);
return NULL;
}
3. Reverse the List
void reverseCircularList(Node** head) {
if (*head == NULL || (*head)->next == *head) return;
Node *prev = NULL, *current = *head, *next;
do {
next = current->next;
current->next = prev;
prev = current;
current = next;
} while (current != *head);
(*head)->next = prev;
*head = prev;
}
4. Sort the List
Sorting a singly circular linked list in-place is more complex. Here, I'll show a simple bubble sort:
void sortCircularList(Node** head) {
if (*head == NULL || (*head)->next == *head) return;
int swapped;
Node *ptr1;
Node *lptr = NULL;
do {
swapped = 0;
ptr1 = *head;
do {
if (ptr1->data > ptr1->next->data) {
int temp = ptr1->data;
ptr1->data = ptr1->next->data;
ptr1->next->data = temp;
swapped = 1;
}
ptr1 = ptr1->next;
} while (ptr1->next != lptr && ptr1->next != *head);
lptr = ptr1;
} while (swapped);
}
Doubly Circular Linked List
1. Length of List
int circularDoublyListLength(Node* head) {
return circularListLength(head);
}
2. Search for a Node
Node* circularDoublySearch(Node* head, int key) {
return circularSearch(head, key);
}
3. Reverse the List
void reverseDoublyCircularList(Node** head) {
if (*head == NULL || (*head)->next == *head) return;
Node *current = *head;
Node *temp = NULL;
do {
temp = current->next;
current->next = current->prev;
current->prev = temp;
current = current->prev;
} while (current != *head);
*head = (*head)->next;
}
4. Sort the List
Sorting a doubly circular linked list can use the same logic as for singly circular, but with added support for previous pointers:
void sortDoublyCircularList(Node** head) {
sortCircularList(head);
if (*head == NULL || (*head)->next == *head) return;
Node* current = (*head)->next;
do {
current->prev = current->prev->next;
current = current->next;
} while (current != *head);
}
Problems:
Beginner Level
Implement a Singly Linked List - Create a basic linked list with operations to add and remove nodes.
Print Elements of a Linked List - Write a function that prints out all the elements of a linked list.
Insert a Node at the Tail of a Linked List - Write a function that inserts a new node at the end of the list.
Delete a Node from a Linked List - Implement a function that can remove a given node from a linked list.
Count Nodes - Create a function that returns the count of nodes present in the linked list.
Intermediate Level
Detect Loop in a Linked List - Implement an algorithm to detect a cycle in a linked list.
Reverse a Linked List - Write a function that reverses the links in a linked list.
Find the Middle of a Linked List - Develop a method to find the middle node of a linked list efficiently.
Remove N-th Node from End - Remove the N-th node from the end of the list in a single pass.
Merge Two Sorted Linked Lists - Merge two sorted linked lists into a single, sorted linked list.
Advanced Level
Detect and Remove Loop - Detect a cycle in a linked list and remove it.
Rotate a Linked List - Rotate the linked list right by k places.
Clone a Linked List with Random Pointer - Clone a linked list where each node has a next and a random pointer.
Add Two Numbers Represented by Linked Lists - Given two numbers represented by two lists, write a function that returns the sum list.
Sort a Linked List - Implement a sorting algorithm to sort a linked list (e.g., merge sort).
Expert Level
Find the Intersection Point of Two Linked Lists - Determine the node where two singly linked lists intersect.
Palindrome Linked List - Check if a linked list forms a palindrome.
Rearrange a Linked List - Rearrange the linked list in a specific pattern (e.g., L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …).
Partition List - Partition the list around a value x, such that all nodes less than x come before all nodes greater than or equal to x.
Multi-level Doubly Linked List Flattening - Flatten a multi-level doubly linked list where in addition to the next and previous pointers, each node has a child pointer, which may point to a separate doubly linked list.
Below are hints on how to approach each linked list problem in C, categorized by difficulty level:
Beginner Level
Implement a Singly Linked List: Begin by defining a struct Node containing an int data field and a Node* next pointer. Implement functions to create a node, insert a node, and display the list. Pay attention to special cases like inserting into an empty list.
Print Elements of a Linked List: Use a simple traversal technique. Start from the head and use a while loop to go through each node until you reach NULL. In each iteration, print the data of the current node.
Insert a Node at the Tail of a Linked List: Traverse the list from the head until you reach the last node, which is identified by its next pointer being NULL. Create a new node and link it to the last node's next.
Delete a Node from a Linked List: Consider two cases: deleting the head node and deleting any other node. For the latter, traverse to the node immediately before the target node, then change its next pointer to skip the target node.
Count Nodes: Start from the head and use a counter variable. Traverse through each node, incrementing the counter until you reach the end of the list (NULL). Return the counter value.
Intermediate Level
Detect Loop in a Linked List: Implement Floyd’s Cycle-Finding Algorithm, also known as the tortoise and the hare approach. Use two pointers, one moving twice as fast as the other; if they meet, a loop exists.
Reverse a Linked List: Use three pointers: previous, current, and next. Initialize previous to NULL and current to the head. In a loop, move current along the list, adjust its next to point to previous, and then update previous and current.
Find the Middle of a Linked List: Use two pointers, one moving twice as fast as the other (slow and fast pointers). By the time the fast pointer reaches the end, the slow pointer will be at the middle.
Remove N-th Node from End: Use two pointers with a gap of n nodes between them. Move both pointers simultaneously until the fast one reaches the end, then modify pointers to remove the target node.
Merge Two Sorted Linked Lists: Use a dummy head and a tail pointer starting at this dummy. Compare nodes from both lists, appending the smaller one to the tail, and advance in that list. Continue until all nodes are consumed.
Advanced Level
Detect and Remove Loop: Detect a loop using Floyd’s Cycle-Finding Algorithm. Once detected, use two pointers to find the start of the loop and then adjust pointers to break the loop.
Rotate a Linked List: Compute the length of the list, adjust k to be within the length if it’s greater, find the k-th last node, make it the new head, and adjust the next pointers to form a proper circular list, then break the circle at the new end.
Clone a Linked List with Random Pointer: Use a hashmap to map original nodes to their clones. First, create the clone nodes with next pointers set. Second, set the random pointers using the hashmap.
Add Two Numbers Represented by Linked Lists: Traverse both lists, adding corresponding digits along with any carry, creating new nodes for the sum list. Handle remaining digits and carry after one list is exhausted.
Sort a Linked List: Implement merge sort due to its efficiency with linked lists. Split the list into halves, recursively sort both halves, and merge them.
Expert Level
Find the Intersection Point of Two Linked Lists: First, get the lengths of both lists. Advance the start of the longer list by the difference in lengths, then move in both lists simultaneously until the nodes match.
Palindrome Linked List: Split the list into two halves, reverse the second half, and then compare node by node. Optionally, reverse the second half again to restore the original list.
Rearrange a Linked List: Find the middle of the list, split into two lists, reverse the second list, and alternately merge nodes from each list.
Partition List: Create two new lists, one for nodes less than x and one for nodes greater or equal to x. Traverse the original list, partitioning the nodes into the two new lists, then merge these lists.
Multi-level Doubly Linked List Flattening: Use a stack or recursion to handle child nodes. Traverse the main level nodes and whenever a child node is encountered, recursively flatten and merge it into the main list.
Solution:
Beginner Level
1. Implement a Singly Linked List
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
void printList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
int main() {
Node* head = NULL;
insertAtHead(&head, 10);
insertAtHead(&head, 20);
insertAtHead(&head, 30);
printList(head);
return 0;
}
2. Print Elements of a Linked List
3. Insert a Node at the Tail of a Linked List
void insertAtTail(Node** head, int data) {
Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
} else {
Node* last = *head;
while (last->next != NULL) {
last = last->next;
}
last->next = newNode;
}
}
4. Delete a Node from a Linked List
void deleteNode(Node** head, int key) {
Node* temp = *head, *prev = NULL;
if (temp != NULL && temp->data == key) {
*head = temp->next;
free(temp);
return;
}
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) return;
prev->next = temp->next;
free(temp);
}
5. Count Nodes
int getCount(Node* head) {
int count = 0;
Node* current = head;
while (current != NULL) {
count++;
current = current->next;
}
return count;
}
Intermediate Level
6. Detect Loop in a Linked List
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
int detectLoop(Node* head) {
Node* slow_p = head, *fast_p = head;
while (slow_p && fast_p && fast_p->next) {
slow_p = slow_p->next;
fast_p = fast_p->next->next;
if (slow_p == fast_p) {
return 1;
}
}
return 0;
}
int main() {
Node* head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);
head->next->next->next = head;
if (detectLoop(head)) {
printf("Loop found\n");
} else {
printf("No loop\n");
}
return 0;
}
7. Reverse a Linked List
void reverseList(Node** head) {
Node* prev = NULL;
Node* current = *head;
Node* next = NULL;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
*head = prev;
}
int main() {
Node* head = NULL;
return 0;
}
8. Find the Middle of a Linked List
Node* findMiddle(Node* head) {
Node* slow = head;
Node* fast = head;
if (head != NULL) {
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
}
return slow;
}
int main() {
Node* head = NULL;
return 0;
}
9. Remove N-th Node from End
void removeNthFromEnd(Node** head, int n) {
Node* first = *head;
Node* second = *head;
for (int i = 0; i < n; ++i) {
if (first == NULL) return;
first = first->next;
}
if (first == NULL) {
*head = (*head)->next;
return;
}
while (first->next != NULL) {
first = first->next;
second = second->next;
}
Node* temp = second->next;
second->next = temp->next;
free(temp);
}
int main() {
Node* head = NULL;
return 0;
}
10. Merge Two Sorted Linked Lists
Node* mergeSortedLists(Node* l1, Node* l2) {
Node dummy;
form the base of the new list
Node* tail = &dummy;
dummy.next = NULL;
while (l1 != NULL && l2 != NULL) {
if (l1->data <= l2->data) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
tail->next = (l1 != NULL) ? l1 : l2;
return dummy.next;
}
int main() {
Node* list1 = NULL;
Node* list2 = NULL;
return 0;
}
Advanced Level
11. Detect and Remove Loop
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
void detectAndRemoveLoop(Node* head) {
Node *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
break;
}
}
if (slow == fast) {
slow = head;
while (slow->next != fast->next) {
slow = slow->next;
fast = fast->next;
}
fast->next = NULL;
}
}
int main() {
Node* head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);
head->next->next->next = createNode(4);
head->next->next->next->next = head->next;
detectAndRemoveLoop(head);
return 0;
}
12. Rotate a Linked List
void rotateList(Node** head, int k) {
if (*head == NULL || k == 0) return;
Node *current = *head;
int len = 1;
while (current->next != NULL) {
len++;
current = current->next;
}
current->next = *head;
k = k % len;
int stepsToNewHead = len - k;
while (stepsToNewHead--) current = current->next;
*head = current->next;
current->next = NULL;
}
int main() {
Node* head = NULL;
return 0;
}
13. Clone a Linked List with Random Pointer
#include <stdio.h>
#include <stdlib.h>
typedef struct RandomNode {
int data;
struct RandomNode* next;
struct RandomNode* random;
} RandomNode;
RandomNode* cloneRandomList(RandomNode* head) {
RandomNode* iter = head, *front = head;
while (iter != NULL) {
front = iter->next;
RandomNode* copy = (RandomNode*)malloc(sizeof(RandomNode));
copy->data = iter->data;
iter->next = copy;
copy->next = front;
iter = front;
}
iter = head;
while (iter != NULL) {
if (iter->random != NULL) {
iter->next->random = iter->random->next;
}
iter = iter->next->next;
}
iter = head;
RandomNode* pseudoHead = (RandomNode*)malloc(sizeof(RandomNode));
RandomNode* copy = pseudoHead;
while (iter != NULL) {
front = iter->next->next;
copy->next = iter->next;
copy = copy->next;
iter->next = front;
iter = front;
}
return pseudoHead->next;
}
int main() {
List function to clone the list and print both to verify
return 0;
}
14. Add Two Numbers Represented by Linked Lists
Node* addTwoLists(Node* first, Node* second) {
Node* result = NULL;
Node** lastPtrRef = &result;
int carry = 0, sum;
while (first != NULL || second != NULL || carry != 0) {
int firstVal = (first != NULL) ? first->data : 0;
int secondVal = (second != NULL) ? second->data : 0;
sum = firstVal + secondVal + carry;
carry = sum / 10;
Node* temp = createNode(sum % 10);
*lastPtrRef = temp;
lastPtrRef = &(temp->next);
if (first != NULL) first = first->next;
if (second != NULL) second = second->next;
}
return result;
}
int main() {
Node* first = NULL;
Node* second = NULL;
return 0;
}
15. Sort a Linked List
Node* mergeSortList(Node* head) {
if (head == NULL || head->next == NULL) return head;
Node* middle = findMiddle(head);
Node* nextOfMiddle = middle->next;
middle->next = NULL;
Node* left = mergeSortList(head);
Node* right = mergeSortList(nextOfMiddle);
return mergeSortedLists(left, right);
}
int main() {
Node* head = NULL;
return 0;
}
Expert Level
16. Find the Intersection Point of Two Linked Lists
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
int getCount(Node* head) {
Node* current = head;
int count = 0;
while (current != NULL) {
count++;
current = current->next;
}
return count;
}
Node* getIntersectionNode(Node* head1, Node* head2) {
int c1 = getCount(head1);
int c2 = getCount(head2);
int d;
if (c1 > c2) {
d = c1 - c2;
return _getIntersectionNode(d, head1, head2);
} else {
d = c2 - c1;
return _getIntersectionNode(d, head2, head1);
}
}
Node* _getIntersectionNode(int d, Node* head1, Node* head2) {
Node* current1 = head1;
Node* current2 = head2;
for (int i = 0; i < d; i++) {
if (current1 == NULL) {
return NULL;
}
current1 = current1->next;
}
while (current1 != NULL && current2 != NULL) {
if (current1 == current2)
return current1;
current1 = current1->next;
current2 = current2->next;
}
return NULL;
}
int main() {
return 0;
}
17. Palindrome Linked List
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* reverseList(Node* head) {
Node* prev = NULL;
Node* current = head;
Node* next = NULL;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
return prev;
}
int isPalindrome(Node* head) {
Node* slow = head, *fast = head;
Node* second_half, *prev_of_slow = head;
Node* midnode = NULL;
int res = 1;
if (head != NULL && head->next != NULL) {
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
prev_of_slow = slow;
slow = slow->next;
}
if (fast != NULL) {
midnode = slow;
slow = slow->next;
}
second_half = reverseList(slow);
res = compareLists(head, second_half);
second_half = reverseList(second_half);
if (midnode != NULL) {
prev_of_slow->next = midnode;
midnode->next = second_half;
} else {
prev_of_slow->next = second_half;
}
}
return res;
}
int compareLists(Node* head1, Node* head2) {
Node* temp1 = head1;
Node* temp2 = head2;
while (temp1 != NULL && temp2 != NULL) {
if (temp1->data == temp2->data) {
temp1 = temp1->next;
temp2 = temp2->next;
} else {
return 0;
}
}
if (temp1 == NULL && temp2 == NULL)
return 1;
return 0;
}
int main() {
return 0;
}
18. Rearrange a Linked List
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
rearrange a linked list L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
void rearrangeList(Node* head) {
if (!head || !head->next) return;
Node* slow = head, *fast = slow->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
Node* head1 = head;
Node* head2 = slow->next;
slow->next = NULL;
head2 = reverseList(head2);
Node* p1 = head1;
Node* p2 = head2;
while (p2) {
Node* temp = p1->next;
p1->next = p2;
p2 = p2->next;
p1->next->next = temp;
p1 = temp;
}
}
int main() {
return 0;
}
19. Partition List
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* partition(Node* head, int x) {
Node* smallerHead = NULL, *smallerLast = NULL;
Node* greaterLast = NULL, *greaterHead = NULL;
Node* equalHead = NULL, *equalLast = NULL;
while (head != NULL) {
if (head->data < x) {
if (smallerHead == NULL) {
smallerHead = smallerLast = head;
} else {
smallerLast->next = head;
smallerLast = head;
}
} else if (head->data == x) {
if (equalHead == NULL) {
equalHead = equalLast = head;
} else {
equalLast->next = head;
equalLast = head;
}
} else {
if (greaterHead == NULL) {
greaterHead = greaterLast = head;
} else {
greaterLast->next = head;
greaterLast = head;
}
}
head = head->next;
}
if (greaterLast != NULL) {
greaterLast->next = NULL;
}
if (smallerHead == NULL) {
if (equalHead == NULL) {
return greaterHead;
}
equalLast->next = greaterHead;
return equalHead;
}
if (equalHead == NULL) {
smallerLast->next = greaterHead;
} else {
smallerLast->next = equalHead;
equalLast->next = greaterHead;
}
return smallerHead;
}
int main() {
return 0;
}
20. Multi-level Doubly Linked List Flattening
#include <stdio.h>
#include <stdlib.h>
typedef struct MultiNode {
int data;
struct MultiNode* next;
struct MultiNode* prev;
struct MultiNode* child;
} MultiNode;
MultiNode* flattenList(MultiNode* head) {
MultiNode* current = head;
MultiNode* tail = head;
while (current) {
MultiNode* child = current->child;
MultiNode* next = current->next;
if (child) {
MultiNode* _tail = flattenList(child);
_tail->next = next;
if (next) next->prev = _tail;
current->next = child;
child->prev = current;
current->child = NULL;
tail = _tail;
} else {
tail = current;
}
current = next;
}
return tail;
}
int main() {
return 0;
}
Frequency of Linked List Questions
In technical interviews, especially for positions involving software development, data structures and algorithms are a major focus. Linked lists are one of several key data structures (alongside arrays, trees, graphs, stacks, and queues). A reasonable estimate might be that linked list questions could constitute around 10-15% of data structure-related questions in an interview, especially for roles focused on backend development or systems programming where efficient data manipulation and understanding of memory are crucial.
Common Linked List Interview Questions
Here are some common linked list questions and an approximate estimate of their prevalence in interviews:
Reverse a Linked List - (3%)
Given a singly or doubly linked list, write a function to reverse the list.
Tests basic linked list manipulation and understanding of pointers.
Detect a Cycle in a Linked List (Floyd’s Cycle Detection) - (2%)
Implement an algorithm to determine if a linked list has a cycle. If it does, find the starting point of the loop.
This problem is critical for understanding two-pointer techniques and their applications.
Merge Two Sorted Linked Lists - (2%)
Remove N-th Node From End of List - (1.5%)
Palindrome Linked List - (1%)
Determine if a linked list is a palindrome.
This problem checks for understanding of multiple data structure manipulations, including reversing a list or using a stack.
Find Intersection Point of Two Linked Lists - (1%)
Add Two Numbers Represented by Linked Lists - (1%)
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each node contains a single digit. Add the two numbers and return the sum as a linked list.
Combines elementary arithmetic with linked list manipulation, testing comprehensive understanding of data structures.