Bird
Raised Fist0
Interview Preplinked-list-advancedmediumAmazonGoogleMicrosoft

Design Linked List (Full Implementation)

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Steps
setup

Initialize dummy head, tail, and size

Create a dummy head node with value 0 and no next node. Set tail pointer to dummy and size counter to 0.

💡 Dummy head simplifies edge cases by providing a fixed start; tail pointer helps efficient tail insertions; size tracks list length for quick index validation.
Line:self.dummy = Node(0) self.tail = self.dummy self.size = 0
💡 The linked list starts empty but ready for operations with dummy and tail pointers set.
📊
Design Linked List (Full Implementation) - Watch the Algorithm Execute, Step by Step
Watching each pointer move and node change live helps you understand how linked list operations work internally, beyond just reading code.
Step 1/24
·Active fillAnswer cell
setup
0
insert
0
1
connect
0
1
connect
0
1
advance
0
1
insert
0
1
3
connect
0
1
3
advance
0
1
3
advance
0
1
3
compare
0
1
3
advance
0
1
3
insert
0
1
3
2
connect
0
1
3
2
advance
0
1
3
2
compare
0
1
3
2
advance
0
1
3
2
compare
0
1
3
2
Result: 2
compare
0
1
3
2
Result: 2
advance
0
1
3
2
Result: 2
detach
0
1
3
Result: 2
shrink
0
1
3
Result: 2
compare
0
1
3
Result: 2
advance
0
1
3
Result: 2
compare
0
1
3
Result: 3

Key Takeaways

Using a dummy head node simplifies edge cases for insertions and deletions at the head.

Without dummy, special code is needed for head operations; dummy unifies logic.

Maintaining a tail pointer allows O(1) insertions at the tail without traversal.

Without tail pointer, adding at tail requires traversing entire list, which is inefficient.

Tracking size enables immediate index validity checks, preventing unnecessary traversal.

Size check quickly rejects invalid indices, saving time and avoiding errors.

Practice

(1/5)
1. Consider the following code snippet implementing the optimal approach to copy a list with random pointers. Given the input list: 1 -> 2 -> 3, where node 1's random points to node 3, node 2's random points to node 1, and node 3's random is null, what is the value of copy_curr.random.val after the last iteration of the separation step (Step 3)?
easy
A. 1
B. 3
C. None (random pointer is null)
D. 2

Solution

  1. Step 1: Trace Step 1 (Interleaving nodes)

    Original list: 1->2->3. After interleaving: 1->1'->2->2'->3->3'.
  2. Step 2: Trace Step 2 (Assign random pointers)

    Node 1's random points to 3, so 1'.random = 3'. Node 2's random points to 1, so 2'.random = 1'. Node 3's random is null, so 3'.random = null.
  3. Step 3: Trace Step 3 (Separate lists)

    After separation, copy_curr starts at 1'. After last iteration, copy_curr is at 3'. Its random pointer is null, so copy_curr.random.val does not exist.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    copy_curr.random is null after last iteration [OK]
Hint: Random pointers point to copied nodes via original's next [OK]
Common Mistakes:
  • Confusing original and copied nodes during separation
  • Off-by-one error in advancing copy_curr
  • Assuming random pointers remain unchanged
2. Consider the following buggy recursive code to convert a binary number in a linked list to an integer. Which line contains the subtle bug that causes incorrect output for single-node lists?
medium
A. Line X: using addition instead of bitwise OR to accumulate bits
B. Line 7: base case check for None node
C. Line 3: __init__ method of ListNode
D. Line 9: recursive call with node.next and updated acc

Solution

  1. Step 1: Identify the accumulation operation

    The code uses bitwise OR instead of addition to combine bits: acc = (acc << 1) | node.val.
  2. Step 2: Understand impact on single-node lists

    Bitwise OR correctly sets bits without carry, ensuring accurate accumulation for all list lengths.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Bitwise OR correctly sets bits without carry, addition may overflow [OK]
Hint: Use bitwise OR, not addition, to accumulate bits [OK]
Common Mistakes:
  • Using + instead of | causes wrong bit accumulation
  • Misunderstanding bitwise operations vs arithmetic
  • Assuming addition and OR are interchangeable for bits
3. The following code attempts to implement the two stacks browser history. Identify the line containing the subtle bug that causes forward navigation to return outdated pages after visiting a new URL.
class BrowserHistory:
    def __init__(self, homepage: str):
        self.back_stack = [homepage]
        self.forward_stack = []

    def visit(self, url: str) -> None:
        self.back_stack.append(url)
        # Missing forward_stack.clear() here

    def back(self, steps: int) -> str:
        while steps > 0 and len(self.back_stack) > 1:
            self.forward_stack.append(self.back_stack.pop())
            steps -= 1
        return self.back_stack[-1]

    def forward(self, steps: int) -> str:
        while steps > 0 and self.forward_stack:
            self.back_stack.append(self.forward_stack.pop())
            steps -= 1
        return self.back_stack[-1]
medium
A. Line where back_stack.append(url) is called in visit
B. Line where forward_stack.clear() should be called but is missing in visit
C. Line where back_stack.pop() is called in back method
D. Line where forward_stack.pop() is called in forward method

Solution

  1. Step 1: Identify visit method behavior

    Visit appends new URL but does not clear forward_stack, so forward history remains outdated.
  2. Step 2: Understand impact on forward navigation

    Without clearing forward_stack, forward() returns stale pages that should have been discarded.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Clearing forward_stack on visit is essential to maintain correct forward history [OK]
Hint: Visit must clear forward history to avoid stale pages [OK]
Common Mistakes:
  • Forgetting to clear forward stack on visit
  • Incorrectly popping from back stack
  • Mismanaging stack boundaries
4. Identify the bug in the following code snippet for splitting a linked list into k parts:
medium
A. Line where curr is advanced without checking if curr is None, causing AttributeError.
B. Line where tails[i].next is set to curr without checking if curr is None.
C. Line where tails[i].next is set to None, which breaks the list prematurely.
D. Line where dummy_heads are created, which wastes extra space.

Solution

  1. Step 1: Analyze pointer advancement

    Inside the inner loop, curr is advanced with curr = curr.next without checking if curr is None.
  2. Step 2: Identify potential error

    If curr is None, accessing curr.next raises AttributeError. The check must happen before advancing curr.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Missing None check before curr = curr.next causes runtime error [OK]
Hint: Always check curr is not None before accessing curr.next [OK]
Common Mistakes:
  • Forgetting None check before pointer advance
  • Incorrectly cutting list by setting next to None too early
  • Misusing dummy heads
5. Suppose the problem is modified so that nodes can be reused multiple times (i.e., after reversing a group, nodes can appear again in subsequent groups). Which of the following changes to the algorithm correctly handles this scenario?
hard
A. Modify the algorithm to create new nodes for each group reversal to avoid modifying original nodes in place.
B. This problem cannot be solved by reversal; instead, use a queue to simulate repeated node usage.
C. Use recursion with memoization to store reversed groups and reuse them without modifying the original list.
D. Use the same iterative reversal approach but reset pointers to allow reusing nodes in multiple groups.

Solution

  1. Step 1: Understand node reuse implication

    Reusing nodes means original nodes must remain unchanged or duplicated to appear multiple times.
  2. Step 2: Evaluate algorithm modifications

    In-place reversal modifies nodes destructively, so creating new nodes for each group is necessary to preserve original nodes.
  3. Step 3: Assess other options

    Resetting pointers (A) breaks list integrity; recursion with memoization (C) is complex and not standard; queue simulation (B) does not solve reversal reuse.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Duplicating nodes preserves original list for reuse [OK]
Hint: Node reuse requires duplication, not in-place reversal [OK]
Common Mistakes:
  • Trying to reuse nodes in place
  • Ignoring list integrity
  • Assuming recursion memoization solves reuse