Linked List Construction
Linked List Construction
Category
Linked Lists
Difficulty
Medium
Problem Statement
Implement a doubly linked list class that supports the following operations:
- setHead(node): Set the head of the list.
- setTail(node): Set the tail of the list.
- insertBefore(node, nodeToInsert): Insert
nodeToInsertbeforenode. - insertAfter(node, nodeToInsert): Insert
nodeToInsertafternode. - insertAtPosition(position, nodeToInsert): Insert at a 1-indexed position.
- removeNodesWithValue(value): Remove all nodes with the given value.
- remove(node): Remove the given node from the list.
- containsNodeWithValue(value): Check if a value exists in the list.
Intuition
A doubly linked list maintains prev and next pointers on each node, plus head and tail references on the list itself. The key challenge is correctly updating all affected pointers during insertions and removals, especially when the operation involves the head or tail of the list. By implementing a robust remove helper and using it within other methods, we reduce code duplication and bugs.
Approach
- Maintain
headandtailpointers on the list. - Each node has
value,prev, andnextproperties. - For
remove(node): update the previous node'snextand the next node'sprev. Handle head/tail cases. - For insertion methods: first remove the node if it is already in the list (to avoid duplicates), then wire it into the correct position by updating the surrounding pointers.
- For
insertAtPosition: traverse from the head to find the node at the given position, then useinsertBefore.
Pseudocode
class DoublyLinkedList:
head = null
tail = null
function remove(node):
if node == head: head = head.next
if node == tail: tail = tail.prev
if node.prev: node.prev.next = node.next
if node.next: node.next.prev = node.prev
node.prev = null
node.next = null
function insertBefore(node, nodeToInsert):
if nodeToInsert == head and nodeToInsert == tail: return
remove(nodeToInsert)
nodeToInsert.prev = node.prev
nodeToInsert.next = node
if node.prev is null:
head = nodeToInsert
else:
node.prev.next = nodeToInsert
node.prev = nodeToInsert
function insertAfter(node, nodeToInsert):
if nodeToInsert == head and nodeToInsert == tail: return
remove(nodeToInsert)
nodeToInsert.prev = node
nodeToInsert.next = node.next
if node.next is null:
tail = nodeToInsert
else:
node.next.prev = nodeToInsert
node.next = nodeToInsert
function setHead(node):
if head is null:
head = node
tail = node
else:
insertBefore(head, node)
function setTail(node):
if tail is null:
setHead(node)
else:
insertAfter(tail, node)
function insertAtPosition(position, nodeToInsert):
if position == 1:
setHead(nodeToInsert)
return
current = head
p = 1
while current and p < position:
current = current.next
p++
if current is not null:
insertBefore(current, nodeToInsert)
else:
setTail(nodeToInsert)
function removeNodesWithValue(value):
current = head
while current is not null:
toRemove = current
current = current.next
if toRemove.value == value:
remove(toRemove)
function containsNodeWithValue(value):
current = head
while current is not null:
if current.value == value: return true
current = current.next
return false
Time & Space Complexity
- setHead, setTail, insertBefore, insertAfter, remove: O(1) time, O(1) space.
- insertAtPosition: O(n) time (must traverse to the position), O(1) space.
- removeNodesWithValue: O(n) time (must check all nodes), O(1) space.
- containsNodeWithValue: O(n) time, O(1) space.
Key Insights
- Always remove a node before re-inserting it to handle the case where the node is already in the list.
- Guard against the edge case where the node to insert is already the only node in the list (it is both head and tail).
- Updating head and tail pointers is the most error-prone part — always check if the affected node is the head or tail.
- A doubly linked list enables O(1) removal given a direct reference to the node, unlike a singly linked list which requires O(n) to find the predecessor.
Edge Cases
- Operating on an empty list.
- Inserting a node that is already in the list (should move, not duplicate).
- Removing the head or the tail.
- Removing the only node in the list.
- Inserting at position 1 (becomes the new head) or at a position beyond the tail (becomes the new tail).
- Calling
removeNodesWithValuewhen no nodes match.