DAY 55: Mastering Linked List Problems β Merge K Lists, Flatten, Clone a List with Random Pointers & Browser History | Linked List


π Introduction
Welcome to Day 55 of my DSA Journey!
After spending an intense few weeks mastering Recursion, Iβve now shifted my focus to one of the most fundamental data structures in DSA β the Linked List.
Transitioning from recursion to linked lists has been both challenging and rewarding, as these concepts are closely related, especially when solving problems that involve traversal and pointer manipulation.
Over the past 3 days, Iβve practiced several key problems on Linked Lists, including: Merge K Sorted Lists, Flatten a Linked List, Clone a Linked List with Random and Next Pointers, and Design Browser History.
Iβm documenting this journey publicly to stay consistent, track my progress, and hopefully help others who are starting their DSA journey from scratch.
π Feel free to follow me on Twitter for real-time updates, problem breakdowns, and coding insights!
π Hereβs What I Covered Over the Last 3 Days:
Day 52
- Merge K Sorted Lists
- Flatten a Linked List
Day 53
- Clone a Linked List with Random and Next Pointers
Day 54
- Design Browser History (using Doubly Linked List)
Now, letβs dive deeper into each of these problems π
Q 1. Merge K Sorted Lists
Given an array (or list) of k linked lists, where each linked list is already sorted in ascending order.
Our goal is to merge all these lists into one sorted linked list and return its head.
Example:
Input:k = 3
List 1 : 1 β 4 β 5
List 2 : 1 β 3 β 4
List 3 : 2 β 6
Output:1 β 1 β 2 β 3 β 4 β 4 β 5 β 6
We take three individually sorted lists and merge them into one single sorted linked list, preserving the order.
Approach:
In this implementation, we use a pairwise merge approach:
- Start with the first linked list as
mergedHead
. - Iteratively merge
mergedHead
with the next list from the array. - Continue until all
k
lists are merged into one. - To merge two sorted linked lists, we use the classic merge technique (like merging two arrays in merge sort).
Code Implementation:
private static Node mergeKSortedLists(ArrayList<Node> list) {
Node mergedHead = null;
// Iteratively merge one list at a time
for (int i = 0; i < list.size(); i++) {
Node tempHead = list.get(i);
mergedHead = merge(mergedHead, tempHead);
}
return mergedHead;
}
// Helper function to merge two sorted linked lists
private static Node merge(Node list1, Node list2) {
Node dummy = new Node(-1);
Node temp = dummy;
while (list1 != null && list2 != null) {
if (list1.value <= list2.value) {
temp.next = list1;
list1 = list1.next;
} else {
temp.next = list2;
list2 = list2.next;
}
temp = temp.next;
}
// Append remaining nodes
if (list1 != null) {
temp.next = list1;
} else {
temp.next = list2;
}
return dummy.next;
}
Time & Space Complexity:
Merging Two Lists:
O(n + m)
wheren
andm
are the lengths of the two lists.Merging K Lists Iteratively: If total nodes =
N
, then worst-case complexity β O(k * N)- Because for each new list, we merge it with the already merged result.
Space Complexity:
O(1)
(No extra space except dummy node; merging is done in-place).
Note: While this solution works fine for small values of k
, it becomes less efficient when k
is large.
Optimized approaches include:
- Min Heap / Priority Queue β
O(N log k)
- Divide and Conquer Merge β
O(N log k)
Final Thoughts:
- This approach is simple, easy to understand, and works well for small
k
. - For interview purposes, always mention the optimized heap-based approach for large
k
. - This problem builds a strong foundation for solving advanced linked list problems such as Merge K Sorted Arrays or External Sorting.
Key takeaway: Start with a simple iterative merge, then scale up to priority queues for efficiency.
Q 2. Flatten a Linked List
Given a Linked List of Linked Lists, where each node has:
- A
next
pointer β points to the next node in the main list. - A
child
pointer β points to a sorted linked list.
The task is to flatten the list into a single sorted linked list using the child pointer.
Example:
Input:
Head β 5 β 10 β 19 β 28
| | |
V V V
7 20 22 β 50
| |
V V
8 35
| |
V V
30 40
Output:5 β 7 β 8 β 10 β 19 β 20 β 22 β 28 β 30 β 35 β 40 β 50
Here, the list has been flattened into a single sorted list using only the child
pointers.
Approach:
We solve this problem using a combination of recursion and merging sorted linked lists:
Base Case
- If the
head
isnull
or thereβs only one node left (i.e.,head.next == null
), thereβs nothing to flatten. - In this case, simply return the
head
.
- If the
Recursive Step
- Recursively call the function for
head.next
. - This will flatten the list starting from the next node onward, ensuring that when recursion unwinds, we already have a flattened list for the right side.
- Recursively call the function for
Merge Current with Flattened
- Now, merge the current nodeβs child list (
head.child
) with the already flattened list from step 2. - We use a helper function (
mergeTwoSortedList
) which works just like merging in merge sort. - During the merge, always pick the smaller node first and link it through the
child
pointer.
- Now, merge the current nodeβs child list (
Remove Next Pointers
- Since the final flattened list should be single-level (connected only via
child
pointers), set allnext
pointers tonull
during merging.
- Since the final flattened list should be single-level (connected only via
Return the Merged List
- After recursion and merging complete, the returned head points to a fully flattened, sorted linked list.
The key idea is:
- Break the problem into smaller parts by recursion.
- Solve one part (flatten right side), then combine (merge) with the current part.
- This divide-and-conquer approach ensures a clean and simple solution.
Code Implementation:
private Node flattenList(Node head) {
if (head == null || head.next == null) {
return head;
}
// Recursively flatten the rest of the list
Node mergedHead = flattenList(head.next);
// Merge current list with flattened result
return mergeTwoSortedList(head, mergedHead);
}
private Node mergeTwoSortedList(Node list1, Node list2) {
Node dummy = new Node(-1);
Node temp = dummy;
while (list1 != null && list2 != null) {
if (list1.data <= list2.data) {
temp.child = list1;
list1 = list1.child;
} else {
temp.child = list2;
list2 = list2.child;
}
temp = temp.child;
temp.next = null; // ensure 'next' is null
}
// Attach the remaining nodes
if (list1 != null) {
temp.child = list1;
} else {
temp.child = list2;
}
return dummy.child;
}
Time & Space Complexity:
- Merging Two Lists: O(n + m), where n and m are the lengths of the two lists.
- Flattening K Lists: If there are N total nodes across all lists, worst-case complexity β O(N * k), where k = number of lists.
- Space Complexity: O(1) (merging is done in-place, excluding recursion stack).
Note: For large k, this solution is less efficient. Optimized approaches include:
- Min Heap / Priority Queue: O(N log k)
- Divide & Conquer Merge: O(N log k)
Final Thoughts:
- The recursive + merge approach is intuitive and excellent for conceptual understanding.
- In interviews, always mention the priority queue method for better scalability.
- This problem strengthens your skills in recursion, merging, and linked list manipulations.
Key Takeaway: Focus on reducing merge operations using heaps or divide & conquer for large input sizes.
Q 3. Clone a Linked List with Random and Next Pointers
We are given a special linked list where each node has two pointers:
- next β points to the next node in the list.
- random β points to any node in the list (or
null
).
The task is to create a deep copy (clone) of this linked list.
- The new list should have entirely new nodes.
- Both
next
andrandom
pointers must be set correctly.
Example:
Input List:
1 β 2 β 3 β 4 β 5
| | |
V V V
3 1 5
- Here, each vertical arrow (
V
) represents a random pointer.- Node
1
β random points to Node3
- Node
2
β random points to Node1
- Node
3
β random points to Node5
- Nodes
4
and5
β random isnull
- Node
Cloned Output List:
1' β 2' β 3' β 4' β 5'
| | |
V V V
3' 1' 5'
Notice:
- The cloned list has all new nodes (
1'
,2'
,3'
, etc.). - Random connections are preserved but point to cloned nodes instead of the original ones.
Approach:
We solve this in 3 main steps:
1. Clone nodes and insert them in between
- Traverse the original list.
- For each node
temp
, create a clonecopyNode
and insert it right aftertemp
.
Example after step 1:1 β 1' β 2 β 2' β 3 β 3' β ...
2. Copy random pointers
- Traverse again.
- If
temp.random != null
, set:temp.next.random = temp.random.next
- This works because
temp.next
is the clone, andtemp.random.next
is the clone of the random node.
3. Separate cloned list from original
- Traverse again and detach cloned nodes (
1'
,2'
, etc.) into a new list. - Restore the original listβs
next
pointers.
Code Implementation:
private static Node copyRandomList(Node head) {
// Step 1: Insert cloned nodes after originals
Node temp = head;
while (temp != null) {
Node copyNode = new Node(temp.value);
copyNode.next = temp.next;
temp.next = copyNode;
temp = temp.next.next;
}
// Step 2: Connect random pointers
temp = head;
while (temp != null) {
Node copyNode = temp.next;
if (temp.random == null) {
copyNode.random = null;
} else {
copyNode.random = temp.random.next;
}
temp = temp.next.next;
}
// Step 3: Detach cloned list
temp = head;
Node dummy = new Node(-1);
Node res = dummy;
while (temp != null) {
res.next = temp.next; // add cloned node
temp.next = temp.next.next; // restore original link
res = res.next;
temp = temp.next;
}
return dummy.next;
}
Time & Space Complexity:
- Step 1 (Insert clones):
O(N)
- Step 2 (Copy random pointers):
O(N)
- Step 3 (Detach clone list):
O(N)
Total Time Complexity: O(N)
- Space Complexity:
O(1)
extra (since weβre not using a HashMap, only dummy nodes and pointers).- The cloned list itself requires new memory for
N
nodes, but no extra auxiliary space.
Final Thoughts:
- This is the optimal solution because it avoids using a
HashMap<Node, Node>
(which costsO(N)
space). - The trick is interweaving cloned nodes with the original ones to reuse their random relationships.
- In interviews, you should first mention the HashMap approach (easy to explain), then move to this O(1) space approach for optimization.
Key Takeaway: Always look for ways to avoid extra space by cleverly reusing existing structures.
Q 4. Design Browser History
We need to design a browser history system that mimics the behavior of real browsers.
The system should support three main operations:
- Visit a URL β Navigate to a new webpage.
- Back(steps) β Go back up to
steps
pages in history. - Forward(steps) β Go forward up to
steps
pages in history.
Example:
Operations:
BrowserHistory("leetcode.com");
visit("google.com");
visit("facebook.com");
visit("youtube.com");
back(1);
back(1);
forward(1);
visit("linkedin.com");
forward(2);
back(2);
back(7);
Output:
null
null
null
null
"facebook.com"
"google.com"
"facebook.com"
null
"linkedin.com"
"google.com"
"leetcode.com"
Approach:
We use a Doubly Linked List to represent the browser history.
- Each node stores a
url
. - Each node has two pointers:
prev
β points to the previous page.next
β points to the next page.
- A
curr
pointer keeps track of the current page.
Operations:
Visit(url):
- Create a new node for the URL.
- Discard all forward history (
curr.next = null
). - Link the new node after
curr
. - Move
curr
to the new node.
Back(steps):
- Move
curr
backward up tosteps
times usingprev
. - Stop if
prev == null
. - Return the current URL.
- Move
Forward(steps):
- Move
curr
forward up tosteps
times usingnext
. - Stop if
next == null
. - Return the current URL.
- Move
Code Implementation:
public class BrowserHistory {
private Node curr;
// Doubly Linked List Node
private static class Node {
String url;
Node next;
Node prev;
public Node(String url) {
this.url = url;
}
}
// Initialize with homepage
public BrowserHistory(String homepage) {
curr = new Node(homepage);
}
// Visit a new page
public void visit(String url) {
curr.next = null; // clear forward history
Node node = new Node(url);
node.prev = curr;
curr.next = node;
curr = node;
}
// Move back in history
public String back(int steps) {
while (steps != 0) {
if (curr.prev == null) break;
curr = curr.prev;
steps--;
}
return curr.url;
}
// Move forward in history
public String forward(int steps) {
while (steps != 0) {
if (curr.next == null) break;
curr = curr.next;
steps--;
}
return curr.url;
}
}
Time & Space Complexity:
- Visit:
O(1)
- Back(steps):
O(steps)
(worst case traversesteps
times) - Forward(steps):
O(steps)
(worst case traversesteps
times)
Overall: Efficient enough since each operation is linear in the number of steps requested.
- Space Complexity:
O(N)
β we store each visited page as a node in the doubly linked list.
Final Thoughts:
- This is a clean, object-oriented design that mimics real-world browser navigation.
- Using a Doubly Linked List allows us to move both backward and forward efficiently.
- In interviews, we can also mention alternative approaches:
- Two Stacks Approach (Back stack + Forward stack).
- ArrayList with pointer (simpler but less flexible for very large histories).
Key Takeaway: Use a doubly linked list when we need efficient bidirectional navigation in data structures.
5. What's next:
Iβm excited to keep growing and sharing along the way! Hereβs whatβs coming up:
- Posting new blog updates every 3 days to share what Iβm learning and building.
- Alongside mastering DSA concepts, Iβm also documenting my web development journey β check out my ongoing Web dev Journey Blog for ReactJS concepts, UI projects, Codes, and more.
- Sharing regular progress and insights on X (Twitter) β feel free to follow me there and join the conversation!
Thanks for being part of this journey!
6. Conclusion:
In this blog, we explored some of the most important Linked List problems that frequently appear in coding interviews and real-world applications:
- β Merge K Sorted Lists β building a foundation for advanced merging techniques.
- β Flatten a Linked List β strengthening recursion and divide & conquer intuition.
- β Clone a Linked List with Random Pointers β mastering deep copy techniques with pointers.
- β Design Browser History β applying linked lists to simulate real-world systems.
Each of these problems not only sharpens mine understanding of linked list manipulation but also helps me to think about optimization trade-offs (e.g., iterative vs. heap-based merging, recursion vs. in-place solutions, doubly linked lists vs. stacks).
π‘ Key Takeaway: Mastering Linked Lists is not about memorizing solutions β itβs about understanding how to navigate, manipulate, and optimize pointer-based structures. Once weβre comfortable, weβll find it much easier to tackle trees, graphs, and even advanced system design problems.
π If youβre on a similar journey, stay consistent, code daily, and donβt shy away from challenging problems. Every step makes you a better problem solver.
Thanks for reading! π
Follow me here (and on Twitter) for more updates as I continue my DSA journey π.
Subscribe to my newsletter
Read articles from Ritik Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Ritik Kumar
Ritik Kumar
π¨βπ» Aspiring Software Developer | MERN Stack Developer.π Documenting my journey in Full-Stack Development & DSA with Java.π Focused on writing clean code, building real-world projects, and continuous learning.