from typing import List
from collections import deque
def is_valid(s: str) -> bool:
count = 0
for c in s:
if c == '(': count += 1
elif c == ')':
count -= 1
if count < 0:
return False
return count == 0
def remove_invalid_parentheses_bfs(s: str) -> List[str]:
res = []
visited = set([s]) # STEP 1
queue = deque([s]) # STEP 1
found = False # STEP 1
while queue: # STEP 2
size = len(queue)
for _ in range(size):
curr = queue.popleft() # STEP 2
if is_valid(curr): # STEP 8,10
res.append(curr) # STEP 8,10
found = True # STEP 8,10
if found:
continue # STEP 9
for i in range(len(curr)): # STEP 3-5,7
if curr[i] not in ('(', ')'):
continue
next_str = curr[:i] + curr[i+1:]
if next_str not in visited:
visited.add(next_str)
queue.append(next_str)
if found: # STEP 11
break
return res
if __name__ == "__main__":
test_str = "()())()"
print(remove_invalid_parentheses_bfs(test_str))
📊
Remove Invalid Parentheses - Watch the Algorithm Execute, Step by Step
Watching the algorithm step-by-step reveals how BFS ensures minimal removals and how pruning works by stopping deeper levels once valid strings are found.
This insight is hard to see from code alone because it requires understanding the search space and pruning logic visually.
✓ Once a valid string is found at a BFS level, deeper levels are not explored, optimizing performance.
This pruning step is subtle in code but clear when watching the queue stop growing after valid strings appear.
✓ Multiple valid strings can exist at the same minimal removal level, and BFS finds all of them.
Seeing multiple valid nodes at the same level helps understand why BFS is preferred over DFS here.
Practice
(1/5)
1. What is the time complexity of the backtracking with bitmask optimization approach for counting beautiful arrangements of size n?
medium
A. O(n * n!)
B. O(n^2)
C. O(n * 2^n)
D. O(n!)
Solution
Step 1: Identify the search space
The algorithm explores permutations of n numbers, which is n! in worst case.
Step 2: Consider pruning and bitmask usage
Bitmask helps track used numbers efficiently, pruning invalid branches but the total number of recursive calls is proportional to n * n! because for each position, up to n choices are tried.
Final Answer:
Option A -> Option A
Quick Check:
Backtracking explores permutations with n choices per position, leading to O(n * n!) complexity [OK]
Hint: Backtracking permutations with bitmask -> O(n * n!) time [OK]
Common Mistakes:
Confusing bitmask with exponential 2^n complexity
Assuming polynomial due to pruning
2. Identify the bug in the following code snippet for counting beautiful arrangements:
medium
A. Line with 'bit = 1 << num' bit shift
B. Line with 'if (used & bit) == 0' check
C. Line with 'if pos > n:' condition
D. Line with recursive call 'backtrack(pos + 1, used | bit)'
Solution
Step 1: Analyze bit shift operation
Bitmask uses 0-based indexing, so bit for number num should be 1 << (num - 1), not 1 << num.
Step 2: Consequence of incorrect bit shift
Incorrect bit shifts cause wrong bits to be set/checked, leading to missing or duplicate counts.
Final Answer:
Option A -> Option A
Quick Check:
Correct bit shift is critical for accurate used-state tracking [OK]
Hint: Bit shifts must be zero-based for bitmask [OK]
Common Mistakes:
Off-by-one bit shifts
Forgetting to unmark used numbers
3. Examine the following buggy code snippet for Expression Add Operators. Which line contains the subtle bug that causes incorrect results on inputs with leading zeros?
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)):
# Bug: missing leading zero check
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
medium
A. Line with 'if i != index and num[index] == '0':' missing here
B. Line with 'curr_str = num[index:i+1]' (substring extraction)
C. Line with 'path[length_before:] = []' (backtracking cleanup)
D. Line with 'path.append('+'); path.append(curr_str)' (operator insertion)
Solution
Step 1: Identify handling of leading zeros
The code lacks the check 'if i != index and num[index] == '0': break' which prevents invalid operands like "05".
Step 2: Confirm bug location
This missing check is critical and should be placed before parsing curr_str to avoid invalid expressions.
Final Answer:
Option A -> Option A
Quick Check:
Missing leading zero check causes invalid operands [OK]
Hint: Leading zero check missing causes invalid operands [OK]
Common Mistakes:
Confusing backtracking cleanup lines as bug
Thinking substring extraction is buggy
4. What is the time complexity of the bitmask-optimized backtracking solution for the N-Queens problem, and why is the common misconception that it is O(n^3) incorrect?
medium
A. O(n^3) because there are three nested loops over rows, columns, and diagonals
B. O(2^n) because bitmasking iterates over all subsets of columns
C. O(n!) because each row places one queen and pruning reduces the search space factorially
D. O(n^2) because each queen placement checks all previous rows and columns
Solution
Step 1: Identify the branching factor per row
Each row places exactly one queen, and pruning avoids invalid columns and diagonals, reducing choices drastically.
Step 2: Understand factorial growth
Because queens cannot share columns, the number of ways to place queens is at most n!; pruning reduces this further but worst-case remains O(n!).
Final Answer:
Option C -> Option C
Quick Check:
Bitmask pruning reduces search to permutations of columns [OK]
Hint: N-Queens search space is permutations, not polynomial loops [OK]
Common Mistakes:
Assuming nested loops imply cubic time
Confusing bitmask subsets with full subsets
Ignoring pruning effect
5. Consider the following code snippet for palindrome partitioning. Which line contains a subtle bug that causes incorrect or duplicate partitions?
def partition(s):
def is_palindrome(left, right):
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
def backtrack(start, path):
if start == len(s):
result.append(path) # Line X
return
for end in range(start, len(s)):
if is_palindrome(start, end):
path.append(s[start:end+1])
backtrack(end+1, path)
path.pop()
result = []
backtrack(0, [])
return result
medium
A. Line 6: palindrome check misses single character substrings
B. Line X: appending path without copying causes shared references
C. Line 12: for loop range should be from start+1 to len(s)+1
D. Line 15: missing path.pop() after backtrack call
Solution
Step 1: Identify how results are stored
Appending path directly stores a reference, so later modifications affect all stored results.
Step 2: Correct approach
Use result.append(path[:]) to append a copy, preserving each partition snapshot.
Final Answer:
Option B -> Option B
Quick Check:
Shared references cause incorrect final partitions [OK]
Hint: Always append a copy of path to results [OK]