3294. Convert Doubly Linked List to Array II 🔒
Problem Description
You are given a random node from a doubly linked list. A doubly linked list is a data structure where each node has:
- A value (
val
) - A pointer to the next node (
next
) - A pointer to the previous node (
prev
)
The challenge is that you're not given the head of the list - you're given some arbitrary node that could be anywhere in the list (beginning, middle, or end).
Your task is to return an array containing all the values from the linked list in their original order, from the first node to the last node.
For example, if the doubly linked list is 1 <-> 2 <-> 3 <-> 4 <-> 5
and you're given the node with value 3
, you need to:
- Find your way back to the head of the list (node with value
1
) - Traverse forward through the entire list
- Return
[1, 2, 3, 4, 5]
The solution works by first traversing backward using the prev
pointers until reaching the head (where prev
is None
), then traversing forward using the next
pointers to collect all values in order.
Intuition
Since we're given an arbitrary node in a doubly linked list and need to return all values in order, the key insight is that we need to find the starting point first.
Think of it like being dropped in the middle of a train - you don't know which car you're in, but you need to list all the cars from the engine to the caboose. What would you do? You'd walk backward through the cars until you reach the engine (the first car), then walk forward through the entire train to list them all.
The same logic applies here. A doubly linked list gives us the ability to move in both directions using prev
and next
pointers. So our strategy becomes:
-
Find the head: Keep moving backward using
node.prev
until we reach a node whereprev
isNone
. This is the head of the list. -
Collect all values: Once at the head, traverse forward using
node.next
and collect each node's value into an array.
The beauty of this approach is its simplicity - we don't need to worry about where we started or keep track of visited nodes. We just need to:
- Go all the way back to the beginning
- Then go all the way forward to the end
This guarantees we'll capture every node exactly once and in the correct order. The time complexity is O(n)
where n
is the total number of nodes, and we only need O(1)
extra space (not counting the output array).
Learn more about Linked List patterns.
Solution Approach
The implementation follows a two-phase approach: finding the head and then collecting all values.
Phase 1: Finding the Head
while node.prev: node = node.prev
Starting from the given node, we traverse backward using the prev
pointer. The loop continues as long as node.prev
exists (is not None
). When the loop terminates, node
points to the head of the linked list - the node that has no previous node.
Phase 2: Collecting Values
ans = []
while node:
ans.append(node.val)
node = node.next
return ans
Once we've found the head, we initialize an empty list ans
to store our result. We then traverse the entire linked list from head to tail:
- Append the current node's value to
ans
- Move to the next node using
node = node.next
- Continue until
node
becomesNone
(we've passed the last node)
The algorithm handles all edge cases naturally:
- If the given node is already the head, the first
while
loop doesn't execute - If the given node is the tail, we still traverse back to the head first
- Single-node lists work correctly since we start and end at the same node
Time and Space Complexity:
- Time:
O(n)
wheren
is the total number of nodes. In the worst case, we traverse the entire list twice - once backward to find the head, once forward to collect values. - Space:
O(1)
extra space for the traversal (not counting the output array which isO(n)
and required for the result).
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given: A doubly linked list 10 <-> 20 <-> 30 <-> 40 <-> 50
where we're given the node containing value 30
.
Step-by-Step Execution:
Phase 1: Finding the Head
Starting position: We're at node 30
- Current node:
30
,node.prev
exists (points to20
) → Move to20
- Current node:
20
,node.prev
exists (points to10
) → Move to10
- Current node:
10
,node.prev
isNone
→ Stop! We've found the head
Phase 2: Collecting Values
Starting from the head node 10
:
- Initialize
ans = []
- Current node:
10
→ Append10
toans
, move tonext
→ans = [10]
- Current node:
20
→ Append20
toans
, move tonext
→ans = [10, 20]
- Current node:
30
→ Append30
toans
, move tonext
→ans = [10, 20, 30]
- Current node:
40
→ Append40
toans
, move tonext
→ans = [10, 20, 30, 40]
- Current node:
50
→ Append50
toans
, move tonext
→ans = [10, 20, 30, 40, 50]
- Current node:
None
→ Stop traversal
Result: Return [10, 20, 30, 40, 50]
This example demonstrates how the algorithm correctly handles being given a middle node. The same logic works regardless of which node we start from - we always find our way back to the head first, then collect all values in order.
Solution Implementation
1"""
2# Definition for a Node.
3class Node:
4 def __init__(self, val, prev=None, next=None):
5 self.val = val
6 self.prev = prev
7 self.next = next
8"""
9
10from typing import List, Optional
11
12
13class Solution:
14 def toArray(self, node: Optional["Node"]) -> List[int]:
15 """
16 Convert a doubly linked list to an array.
17
18 Args:
19 node: Any node in the doubly linked list
20
21 Returns:
22 List containing all values from the doubly linked list in order
23 """
24 # Navigate to the head of the doubly linked list
25 # by traversing backwards until we find a node with no previous node
26 while node and node.prev:
27 node = node.prev
28
29 # Initialize result array to store the values
30 result = []
31
32 # Traverse forward from the head to collect all values
33 while node:
34 result.append(node.val)
35 node = node.next
36
37 return result
38
1/*
2// Definition for a Node.
3class Node {
4 public int val;
5 public Node prev;
6 public Node next;
7};
8*/
9
10class Solution {
11 /**
12 * Converts a doubly linked list to an array.
13 * Given any node in the doubly linked list, returns an array containing
14 * all values from the head to the tail of the list.
15 *
16 * @param node Any node in the doubly linked list
17 * @return Array containing all values in the list from head to tail
18 */
19 public int[] toArray(Node node) {
20 // Edge case: if the input node is null, return empty array
21 if (node == null) {
22 return new int[0];
23 }
24
25 // Step 1: Navigate to the head of the doubly linked list
26 // Keep moving to previous nodes until we reach the head (where prev is null)
27 Node head = node;
28 while (head.prev != null) {
29 head = head.prev;
30 }
31
32 // Step 2: Collect all values from head to tail
33 List<Integer> resultList = new ArrayList<>();
34 Node current = head;
35 while (current != null) {
36 resultList.add(current.val);
37 current = current.next;
38 }
39
40 // Step 3: Convert the ArrayList to a primitive int array
41 int[] resultArray = new int[resultList.size()];
42 for (int i = 0; i < resultList.size(); i++) {
43 resultArray[i] = resultList.get(i);
44 }
45
46 return resultArray;
47 }
48}
49
1/**
2 * Definition for doubly-linked list.
3 * class Node {
4 * int val;
5 * Node* prev;
6 * Node* next;
7 * Node() : val(0), next(nullptr), prev(nullptr) {}
8 * Node(int x) : val(x), next(nullptr), prev(nullptr) {}
9 * Node(int x, Node *prev, Node *next) : val(x), next(next), prev(prev) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Converts a doubly-linked list to an array.
16 * Given any node in the doubly-linked list, returns an array containing
17 * all values from the list in order from head to tail.
18 *
19 * @param node - Any node in the doubly-linked list (can be head, tail, or middle node)
20 * @return vector<int> - Array containing all values in the list from head to tail
21 */
22 vector<int> toArray(Node* node) {
23 // Edge case: if node is null, return empty array
24 if (!node) {
25 return {};
26 }
27
28 // Step 1: Navigate to the head of the doubly-linked list
29 // Keep moving to previous nodes until we reach the head (where prev is null)
30 while (node->prev != nullptr) {
31 node = node->prev;
32 }
33
34 // Step 2: Traverse from head to tail and collect all values
35 vector<int> result;
36 while (node != nullptr) {
37 result.push_back(node->val);
38 node = node->next;
39 }
40
41 return result;
42 }
43};
44
1/**
2 * Definition for _Node.
3 * class _Node {
4 * val: number
5 * prev: _Node | null
6 * next: _Node | null
7 *
8 * constructor(val?: number, prev? : _Node, next? : _Node) {
9 * this.val = (val===undefined ? 0 : val);
10 * this.prev = (prev===undefined ? null : prev);
11 * this.next = (next===undefined ? null : next);
12 * }
13 * }
14 */
15
16/**
17 * Converts a doubly linked list to an array of values
18 * @param node - Any node in the doubly linked list (can be head, middle, or tail)
19 * @returns An array containing all values from the linked list in order
20 */
21function toArray(node: _Node | null): number[] {
22 // Navigate to the head of the linked list by traversing backwards
23 while (node && node.prev) {
24 node = node.prev;
25 }
26
27 // Initialize result array to store node values
28 const result: number[] = [];
29
30 // Traverse the linked list from head to tail and collect all values
31 for (; node; node = node.next) {
32 result.push(node.val);
33 }
34
35 return result;
36}
37
Time and Space Complexity
The time complexity is O(n)
, where n
is the number of nodes in the doubly linked list. This is because:
- The first while loop traverses backwards from the given node to find the head of the list, which takes at most
O(n)
operations - The second while loop traverses forward through all nodes from head to tail, which takes exactly
O(n)
operations - In total, we visit each node at most twice, resulting in
O(n) + O(n) = O(2n) = O(n)
time complexity
The space complexity is O(1)
if we exclude the space used by the answer array ans
. The algorithm only uses a constant amount of extra space for the node
pointer variable. If we include the answer array, the space complexity would be O(n)
since we store all n
node values in the list.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Null Node Handling
One of the most common pitfalls is not properly handling the case when the input node is None
. If the function receives a None
input, attempting to access node.prev
will raise an AttributeError
.
Problematic Code:
def toArray(self, node: Optional["Node"]) -> List[int]:
# This will crash if node is None
while node.prev: # AttributeError if node is None
node = node.prev
Solution:
def toArray(self, node: Optional["Node"]) -> List[int]:
# Handle None input gracefully
if not node:
return []
while node.prev:
node = node.prev
result = []
while node:
result.append(node.val)
node = node.next
return result
2. Infinite Loop in Corrupted Lists
If the doubly linked list has a cycle in the backward direction (corrupted data structure), the algorithm will get stuck in an infinite loop while searching for the head.
Example of Corrupted List:
Node1 <-> Node2 <-> Node3 ^ | |____________________|
Solution with Cycle Detection:
def toArray(self, node: Optional["Node"]) -> List[int]:
if not node:
return []
# Use a set to detect cycles while finding the head
visited = set()
while node.prev and id(node) not in visited:
visited.add(id(node))
node = node.prev
# Collect values going forward
result = []
visited.clear()
while node and id(node) not in visited:
visited.add(id(node))
result.append(node.val)
node = node.next
return result
3. Memory Inefficiency with Large Lists
While the basic solution is correct, for very large lists, repeatedly appending to a list can cause multiple memory reallocations. Though Python handles this reasonably well, in performance-critical scenarios, pre-allocating space could be beneficial.
Optimized Approach for Known Size:
def toArray(self, node: Optional["Node"]) -> List[int]:
if not node:
return []
# Find head and count nodes simultaneously
head = node
count = 1
# Count backward
while head.prev:
head = head.prev
count += 1
# Count forward from original node
temp = node.next
while temp:
temp = temp.next
count += 1
# Pre-allocate result array
result = [0] * count
# Fill the array
i = 0
while head:
result[i] = head.val
head = head.next
i += 1
return result
The most critical pitfall to avoid is the null node handling, as this will cause immediate runtime errors. The cycle detection is important for defensive programming against corrupted data structures, though properly implemented doubly linked lists shouldn't have cycles.
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!