💡 The path now has three segments; one more segment is needed.
traverse
Pop state: start=8, path=['255', '255', '11']
The algorithm pops the state to explore the last segment starting at index 8.
💡 The last segment completes the IP address if valid.
Line:start, path = stack.pop()
💡 We are now choosing the final segment.
compare
Try segment length 3 at start=8: '135'
The algorithm tries the segment '135' starting at index 8 and validates it as the last segment.
💡 The last segment must consume the remaining characters and be valid.
Line:segment = s[start:start+length]
if (segment.startswith('0') and len(segment) > 1) or (length == 3 and int(segment) > 255):
continue
💡 A valid last segment completes the IP address.
insert
Add complete IP '255.255.11.135' to results
Since 4 segments are formed and the entire string is consumed, the algorithm adds '255.255.11.135' to the results.
💡 Collecting valid IP addresses is the goal of the algorithm.
Line:if len(path) == 4 and start == len(s):
res.append(".".join(path))
💡 Complete valid IP addresses are collected here.
traverse
Pop state: start=9, path=['255', '255', '111']
The algorithm pops the state to explore the last segment starting at index 9.
💡 Final segment exploration to complete IP.
Line:start, path = stack.pop()
💡 We are now choosing the final segment for the second valid IP.
compare
Try segment length 2 at start=9: '35'
The algorithm tries the segment '35' starting at index 9 and validates it as the last segment.
💡 The last segment must be valid and consume all remaining characters.
Line:segment = s[start:start+length]
if (segment.startswith('0') and len(segment) > 1) or (length == 3 and int(segment) > 255):
continue
💡 Valid last segments complete the IP address.
insert
Add complete IP '255.255.111.35' to results
The algorithm adds '255.255.111.35' to the results as a valid IP address with 4 segments covering the entire string.
💡 Collecting all valid IP addresses found.
Line:if len(path) == 4 and start == len(s):
res.append(".".join(path))
💡 All valid IP addresses are collected here.
from typing import List
def restore_ip_addresses(s: str) -> List[str]:
res = []
stack = [(0, [])] # (start index, path) # STEP 1
while stack:
start, path = stack.pop() # STEP 2, 5, 8, 11, 14, 16
if len(path) == 4: # STEP 13, 16
if start == len(s):
res.append(".".join(path))
continue
remaining_segments = 4 - len(path)
remaining_chars = len(s) - start
if remaining_chars < remaining_segments or remaining_chars > 3 * remaining_segments:
continue
for length in range(1, 4): # STEP 3, 6, 9, 12, 15
if start + length > len(s):
break
segment = s[start:start+length]
if (segment.startswith('0') and len(segment) > 1) or (length == 3 and int(segment) > 255):
continue
stack.append((start + length, path + [segment])) # STEP 4, 7, 10
return res
if __name__ == "__main__":
test_input = "25525511135"
print(restore_ip_addresses(test_input))
📊
Restore IP Addresses - Watch the Algorithm Execute, Step by Step
Watching each segment choice and pruning decision helps you understand how the algorithm efficiently avoids invalid IP segments and collects valid addresses.
Step 1/16
·Active fill★Answer cell
enterings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Remaining
start=0, path=[]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
start=0, path=[]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='2'segment='25'segment='255'
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='2'segment='25'segment='255'
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
start=3, path=['255']
Partial: [255]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='2'segment='25'segment='255'
Partial: [255]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='2'segment='25'segment='255'
Partial: [255]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
start=6, path=['255', '255']
Partial: [255, 255]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='11'segment='111'
Partial: [255, 255]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='11'segment='111'
Partial: [255, 255]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
start=8, path=['255', '255', '11']
Partial: [255, 255, 11]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='135'
Partial: [255, 255, 11]
backtrackings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='135'
Partial: [255, 255, 11, 135]
Found 1: [255.255.11.135]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
start=9, path=['255', '255', '111']
Partial: [255, 255, 111]
Found 1: [255.255.11.135]
choosings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='35'
Partial: [255, 255, 111]
Found 1: [255.255.11.135]
backtrackings="25525511135"
Call Path (current → root)
restore_ip_addresses(s='25525511135')
Tried
segment='35'
Partial: [255, 255, 111, 35]
Found 2: [255.255.11.135] [255.255.111.35]
Key Takeaways
✓ The algorithm uses a stack to iteratively explore all possible segmentations, avoiding recursion overhead.
This is hard to see from code alone because the stack manipulation and pruning logic are intertwined.
✓ Pruning based on remaining characters and segments drastically reduces the search space.
Visualizing pruning decisions clarifies why some paths are abandoned early.
✓ Valid segments must not start with zero unless they are '0', and must be <= 255, which guides segment validation.
Seeing each segment validation step helps understand these constraints concretely.
Practice
(1/5)
1. You need to place n queens on an nxn chessboard so that no two queens threaten each other (no two queens share the same row, column, or diagonal). Which algorithmic approach guarantees finding all valid solutions efficiently by pruning invalid partial placements early?
easy
A. Backtracking with pruning to avoid columns and diagonals already attacked
B. Greedy algorithm that places queens row by row choosing the first safe column
C. Dynamic programming to count valid queen placements using subproblems
D. Breadth-first search exploring all board states level by level
Solution
Step 1: Understand problem constraints
The problem requires placing queens so none attack each other, which involves checking columns and diagonals.
Step 2: Identify algorithm that prunes invalid states early
Backtracking with pruning efficiently avoids exploring invalid partial solutions by tracking attacked columns and diagonals.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking prunes invalid states early [OK]
Hint: Pruning columns and diagonals is key to efficient search [OK]
Common Mistakes:
Thinking greedy placement always works
Assuming DP applies directly
Confusing BFS with pruning
2. You are given a partially filled 9x9 grid where each row, column, and 3x3 sub-box must contain digits 1 through 9 without repetition. Which algorithmic approach guarantees finding a valid solution if one exists?
easy
A. Greedy algorithm that fills cells with the first valid digit found
B. Breadth-first search exploring all possible digit placements simultaneously
C. Dynamic programming by storing subproblems of partially filled grids
D. Backtracking with constraint propagation and heuristic ordering of empty cells
Solution
Step 1: Understand problem constraints
The problem requires filling cells respecting row, column, and box constraints, which is a classic constraint satisfaction problem.
Step 2: Identify algorithm that guarantees solution
Backtracking with constraint propagation prunes invalid choices early, and heuristic ordering reduces branching, ensuring completeness and efficiency.
Final Answer:
Option D -> Option D
Quick Check:
Constraint propagation + heuristic ordering is standard for Sudoku solvers [OK]
Hint: Constraint satisfaction problems need backtracking with pruning [OK]
Common Mistakes:
Assuming greedy or BFS alone can solve Sudoku efficiently
3. You are given a 2D grid of letters and a list of words. You need to find all words that can be formed by sequentially adjacent letters (horizontally or vertically) without reusing the same cell in a single word. Which algorithmic approach best guarantees an efficient solution for this problem?
easy
A. Use dynamic programming to store subproblems of partial word matches.
B. Use a greedy search that picks the next letter with the highest frequency in the grid.
C. Use backtracking combined with a Trie data structure to prune invalid prefixes early.
D. Search each word independently using DFS without any prefix pruning.
Solution
Step 1: Understand problem constraints
The problem requires searching multiple words in a grid with adjacency and no reuse constraints, which is expensive if done naively.
Step 2: Identify efficient pruning technique
Backtracking with a Trie allows pruning search paths early when no word prefix matches, avoiding redundant searches.
Final Answer:
Option C -> Option C
Quick Check:
Trie pruning reduces search space drastically compared to naive DFS [OK]
Hint: Trie + backtracking prunes invalid prefixes early [OK]
Common Mistakes:
Thinking greedy or DP alone can solve adjacency constraints efficiently.
4. What is the time complexity of the optimal palindrome partitioning algorithm that uses backtracking with dynamic palindrome checks by expanding around centers for palindrome verification on a string of length n?
medium
A. O(n^2) because palindrome checks are done in constant time using DP
B. O(n^3) due to checking all substrings and palindrome verification
C. O(2^n) since all partitions are generated without extra palindrome checks
D. O(n * 2^n) because each character can start a partition and palindrome checks are O(n)
Solution
Step 1: Identify number of partitions
There are up to 2^(n-1) ways to partition a string of length n.
Step 2: Analyze palindrome check cost
Using expand around center, palindrome check per substring is O(n) worst case, done during backtracking.
Final Answer:
Option D -> Option D
Quick Check:
Combining partitions and palindrome checks yields O(n * 2^n) [OK]
Hint: Partitions grow exponentially; palindrome checks add linear factor [OK]
Common Mistakes:
Assuming palindrome checks are O(1) without DP
Confusing total partitions with time complexity
Ignoring recursion stack space
5. Consider the following code snippet intended to generate unique permutations of an array with duplicates. Which line contains a subtle bug that causes duplicate permutations to be generated?
def permuteUnique(nums):
nums.sort()
res = []
def backtrack(start=0):
if start == len(nums):
res.append(nums[:])
return
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]:
continue
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start]
backtrack()
return res
medium
A. Line with 'if i > start and nums[i] == nums[i-1]: continue' (duplicate skipping condition)
B. Line with 'nums.sort()' (sorting input before recursion)
C. Line with 'nums[start], nums[i] = nums[i], nums[start]' before recursion (swap)
D. Line with 'for i in range(start, len(nums)):' (iteration over indices)
Solution
Step 1: Analyze duplicate skipping condition
The condition 'if i > start and nums[i] == nums[i-1]: continue' attempts to skip duplicates but does not consider whether the previous duplicate was used, causing some duplicates to be missed.
Step 2: Verify other lines
Sorting input is correct; swapping before and after recursion is correct; iteration over indices is standard. The bug is in the duplicate skipping logic.