💡 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. Consider the following Python code snippet implementing the optimal backtracking approach for Expression Add Operators. Given input num = "123" and target = 6, what is the content of the result list after the function completes?
from typing import List
def addOperators(num: str, target: int) -> List[str]:
res = []
path = []
def backtrack(index: int, evaluated: int, last_operand: int):
if index == len(num):
if evaluated == target:
res.append(''.join(path))
return
for i in range(index, len(num)):
if i != index and num[index] == '0':
break
curr_str = num[index:i+1]
curr = int(curr_str)
length_before = len(path)
if index == 0:
path.append(curr_str)
backtrack(i+1, curr, curr)
path[length_before:] = []
else:
path.append('+'); path.append(curr_str)
backtrack(i+1, evaluated + curr, curr)
path[length_before:] = []
backtrack(0, 0, 0)
return res
easy
A. ["1+23", "12+3"]
B. ["123"]
C. ["1+2+3", "123"]
D. ["1+2+3"]
Solution
Step 1: Trace backtracking calls for num="123", target=6
At index=0, path starts with "1". Then at index=1, add '+' and "2", evaluated=3. Then at index=2, add '+' and "3", evaluated=6, matches target, so "1+2+3" is added.
Step 2: Check other possible expressions
"123" alone evaluates to 123, not 6. "1+23"=24, "12+3"=15, none equal 6. So only "1+2+3" is in result.
Final Answer:
Option D -> Option D
Quick Check:
Only "1+2+3" sums to 6 [OK]
Hint: Only "1+2+3" equals 6 with given operators [OK]
Common Mistakes:
Assuming "123" or "1+23" equals target
2. Given the following code snippet for getPermutation, what is the returned value when calling getPermutation(3, 3)?
easy
A. '123'
B. '312'
C. '213'
D. '231'
Solution
Step 1: Trace factorial array computation
factorial = [1, 1, 2] for n=3.
Step 2: Calculate indices and build result
k=3 -> k=2 zero-based. For i=2: idx=2//2=1, pick '2'. For i=1: k=2%2=0, idx=0//1=0, pick '1'. For i=0: k=0%1=0, idx=0//1=0, pick '3'. Result = '213'.
Hint: Remember to convert k to zero-based before indexing [OK]
Common Mistakes:
Off-by-one error in k adjustment
Wrong index calculation in loop
Not popping used numbers
3. Given the following code for generating unique permutations, what is the final output when the input is [1,1,2]?
from typing import List
def permuteUnique(nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
def backtrack(start=0):
if start == len(nums):
res.append(nums[:])
return
seen = set()
for i in range(start, len(nums)):
if nums[i] in seen:
continue
seen.add(nums[i])
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start]
backtrack()
return res
print(permuteUnique([1,1,2]))
easy
A. [[1,2,1],[1,1,2],[2,1,1]]
B. [[1,1,2],[2,1,1],[1,2,1]]
C. [[1,1,2],[1,2,1],[2,1,1]]
D. [[2,1,1],[1,1,2],[1,2,1]]
Solution
Step 1: Trace initial sorting and first level of recursion
Input sorted to [1,1,2]. At start=0, i iterates over 0 to 2. First, swap nums[0] with nums[0] (1), add 1 to seen, recurse.
Step 2: Trace subsequent recursion and skipping duplicates
At start=1, i iterates 1 to 2. Swap nums[1] with nums[1] (1), add 1 to seen, recurse. Then swap nums[2] with nums[1] (2), add 2 to seen, recurse. Duplicate 1 at i=2 skipped due to seen.
Final Answer:
Option C -> Option C
Quick Check:
Output matches expected unique permutations in lex order [OK]
Hint: Sort input and skip duplicates with seen set [OK]
Common Mistakes:
Misordering output permutations due to swap order
4. 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
5. Examine the following snippet from an optimized Sudoku solver. Which line contains a subtle bug that can cause incorrect solutions or failure to backtrack properly?
def backtrack():
if not empties:
return True
empties.sort(key=lambda x: len(get_candidates(x[0], x[1])))
r, c = empties[0]
for ch in get_candidates(r, c):
board[r][c] = ch
rows[r].add(ch)
cols[c].add(ch)
boxes[(r//3)*3 + c//3].add(ch)
empties.pop(0)
if backtrack():
return True
# Undo changes
board[r][c] = '.'
rows[r].remove(ch)
cols[c].remove(ch)
boxes[(r//3)*3 + c//3].remove(ch)
empties.insert(0, (r, c))
return False
medium
A. Line that pops the first empty cell from empties
B. Line that sorts empties by candidate count
C. Line that adds ch to rows[r]
D. Line that resets board[r][c] to '.' after backtracking
Solution
Step 1: Identify mutation of empties list
The line popping empties[0] removes the cell but is never restored on backtrack failure.
Step 2: Consequence of missing restoration
Without re-adding the cell to empties after failed recursion, future calls miss this cell, causing incorrect state and solutions.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking requires undoing all changes including empties list [OK]
Hint: Backtracking must undo all state changes, including empties list [OK]
Common Mistakes:
Forgetting to restore empties after pop
Assuming board reset is sufficient
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.