LeetCode 382: Linked List Random Node Solution in Python – A Step-by-Step Guide
Imagine you’re given a linked list—like 1→2→3—and you need to pick a node’s value randomly, with each node having an equal chance, but you can only traverse it once and don’t know its length upfront. That’s the challenge of LeetCode 382: Linked List Random Node, a medium-level problem that’s all about randomness in a constrained structure. Using Python, we’ll explore two solutions: the Best Solution—reservoir sampling for O(n) time and O(1) space—and an Alternative Solution—list conversion at O(n) time and O(n) space. With examples, clear code breakdowns, and a friendly vibe, this guide will help you pick that random node. Let’s get rolling!
What Is LeetCode 382: Linked List Random Node?
LeetCode 382: Linked List Random Node provides a singly linked list, and your task is to implement a Solution
class that can return a random node’s value with uniform probability. You’re given the head of the list during initialization, and getRandom can be called multiple times. It’s a design problem that tests your ability to handle randomness with limited access!
Problem Statement
- Class: Solution
- Methods:
- __init__(head): Initialize with the linked list head.
- getRandom(): Return a random node’s value with equal probability.
- Rules:
- Linked list has at least one node.
- Use only one pass over the list for getRandom.
- Each node has an equal chance (1/n) of being chosen.
Constraints
- Number of nodes: 1 to 10⁴.
- -10⁴ <= Node.val <= 10⁴
- At most 10⁴ calls to getRandom.
Examples
- Input: ["Solution","getRandom","getRandom","getRandom"], [[[1,2,3]],[],[],[]]
- Output: [null,1 or 2 or 3,1 or 2 or 3,1 or 2 or 3]
- Init: 1→2→3.
- getRandom(): 1, 2, or 3 (each 1/3 chance).
- Input: ["Solution","getRandom"], [[[1]],[]]
- Output: [null,1]
- Init: 1, getRandom(): 1 (only option).
These show the random pick—let’s solve it!
Understanding the Problem: Random Node Selection
To tackle LeetCode 382 in Python, we need a class that: 1. Initializes with a linked list head. 2. Returns a random node value with uniform probability (1/n). 3. Operates efficiently without knowing the list length upfront.
A naive approach might store all nodes and pick randomly, but that uses O(n) space. We’ll use:
- Best Solution: Reservoir sampling—O(n) time, O(1) space—optimal and elegant.
- Alternative Solution: List conversion—O(n) time, O(n) space—simple but memory-heavy.
Let’s optimize with the best solution!
Best Solution: Reservoir Sampling
Why This Is the Best Solution
The reservoir sampling approach runs in O(n) time with O(1) space by traversing the list once and maintaining a single “reservoir” value, updating it with a probability that ensures uniformity. It’s the most efficient—meeting the one-pass requirement and minimizing memory while achieving true randomness!
How It Works
Think of it like a lottery with a single winner:
- Reservoir: Start with the first node’s value as the chosen one.
- Traverse: For the ith node (1-based index):
- Keep the current reservoir value with probability (i-1)/i.
- Replace it with the new node’s value with probability 1/i.
- Random: Use random.randint to decide.
- Result: After one pass, the reservoir holds a uniformly random value.
It’s like picking a winner as you go, adjusting odds dynamically!
Step-by-Step Example
For head = 1→2→3
:
- Init: Reservoir = 1, i = 1.
- Node 2 (i=2):
- Probability 1/2 to replace: Random [0,1], e.g., 0 → Reservoir = 2 (1/2 chance each for 1, 2).
- Node 3 (i=3):
- Probability 1/3 to replace: Random [0,2], e.g., 2 → Reservoir unchanged (2 or 1), or 0/1 → 3.
- Each has 1/3 chance after full pass.
- Result: 1, 2, or 3 (1/3 probability each).
For head = 1→2
:
- Reservoir = 1, i=1.
- i=2: 1/2 chance to stay 1, 1/2 to become 2.
- Result: 1 or 2 (1/2 each).
Code with Explanation
Here’s the Python code using reservoir sampling:
import random
class Solution:
def __init__(self, head: Optional[ListNode]):
self.head = head
def getRandom(self) -> int:
# Reservoir sampling
reservoir = self.head.val
curr = self.head.next
i = 2 # 1-based index for probability
while curr:
# With probability 1/i, replace reservoir
if random.randint(0, i - 1) == 0:
reservoir = curr.val
curr = curr.next
i += 1
return reservoir
Explanation
- Lines 5-6: __init__: Store head of linked list.
- Lines 8-17: getRandom:
- Start with head’s value as reservoir.
- Traverse from second node (curr = head.next).
- For ith node, replace reservoir with 1/i probability using random.randint.
- Increment i, move to next node.
- Time: O(n)—single pass through list.
- Space: O(1)—only reservoir and counters.
This is like a random lottery with one winner—light and fast!
Alternative Solution: List Conversion
Why an Alternative Approach?
The list conversion solution runs in O(n) time for initialization and O(1) for getRandom but uses O(n) space by storing all node values in a list during init. It’s simpler—great for small lists or when space isn’t a concern, though it doesn’t meet the one-pass ideal!
How It Works
- Init: Convert linked list to a list.
- GetRandom: Pick random index from list.
- Result: Return value at that index.
Step-by-Step Example
For head = 1→2→3
:
- Init: List = [1, 2, 3].
- getRandom:
- Random index [0, 2], e.g., 1 → 2 (1/3 chance each).
- Result: 1, 2, or 3.
For head = 1
:
- Init: List = [1].
- getRandom: Return 1.
Code with Explanation
import random
class Solution:
def __init__(self, head: Optional[ListNode]):
# Convert linked list to list during initialization
self.values = []
curr = head
while curr:
self.values.append(curr.val)
curr = curr.next
def getRandom(self) -> int:
# Return random element from list
return random.choice(self.values)
Explanation
- Lines 5-10: __init__:
- Traverse list, store values in self.values—O(n).
- Lines 12-14: getRandom:
- Use random.choice on list—O(1).
- Time: O(n) init, O(1) getRandom.
- Space: O(n)—stores all values.
It’s a straightforward random picker—memory-heavy!
Comparing the Two Solutions
- Reservoir Sampling (Best):
- Time: O(n) per getRandom—single pass.
- Space: O(1)—minimal.
- Pros: Space-efficient, one-pass.
- Cons: Slightly more complex logic.
- List Conversion (Alternative):
- Time: O(n) init, O(1) getRandom.
- Space: O(n)—list.
- Pros: Simple, fast getRandom.
- Cons: High space, preprocesses all.
Reservoir sampling wins for space and elegance!
Additional Examples and Edge Cases
- Single node: Both return its value.
- Long list: Reservoir scales better in space.
- Repeated calls: Both maintain uniformity.
Reservoir is optimal, list is memory-intensive.
Complexity Breakdown
- Reservoir Sampling:
- Time: O(n) per call.
- Space: O(1).
- List Conversion:
- Time: O(n) init, O(1) per call.
- Space: O(n).
Reservoir excels for space efficiency.
Key Takeaways
- Reservoir Sampling: Random in one pass—lean and smart!
- List Conversion: Store and pick—clear but heavy!
- Randomness: Probability meets constraints.
- Python Tip: Random rocks—see [Python Basics](/python/basics).
Final Thoughts: Pick That Node
LeetCode 382: Linked List Random Node in Python is a randomness challenge. Reservoir sampling is the efficiency champ, while list conversion offers simplicity at a space cost. Want more? Try LeetCode 380: Insert Delete GetRandom O(1) or LeetCode 398: Random Pick Index. Ready to roll? Visit Solve LeetCode 382 on LeetCode and pick that random node today!