💡 The combination is now valid and ready to be added.
fill_row
Add new combination [3, 4] to results
Add the combination [3, 4] to the results list.
💡 Each valid combination is recorded immediately.
Line:result.append(comb[:])
💡 The results list now contains all six combinations.
compare
Attempt to find next increment position
Set i=1 to check if comb[1] (4) can be incremented, but it is max, so decrement i to 0; comb[0] (3) is also max, decrement i to -1.
💡 When no elements can be incremented, the algorithm terminates.
Line:while i >= 0 and comb[i] == n - k + i + 1:
i -= 1
if i < 0:
break
💡 The algorithm has generated all combinations and stops.
from typing import List
def combine(n: int, k: int) -> List[List[int]]:
result = []
comb = list(range(1, k + 1)) # STEP 1: Initialize combination
while True:
result.append(comb[:]) # STEP 2,7,12,18,23,29: Add current combination
i = k - 1 # STEP 3,8,13,19,24,29: Start from last index
while i >= 0 and comb[i] == n - k + i + 1: # STEP 4,9,14,15,25,26,30: Find incrementable element
i -= 1
if i < 0: # STEP 30: No more increments, break
break
comb[i] += 1 # STEP 5,10,16,21,27: Increment element
for j in range(i + 1, k): # STEP 6,11,17,22,28: Reset elements to right
comb[j] = comb[j - 1] + 1
return result
if __name__ == '__main__':
print(combine(4, 2)) # Output: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
📊
Combinations (Choose K from N) - Watch the Algorithm Execute, Step by Step
Watching this step-by-step reveals how the algorithm efficiently finds the next valid combination without generating duplicates or missing any, which is hard to grasp from code alone.
Step 1/30
·Active fill★Answer cell
enteringn=4k=2
Call Path (current → root)
combine(4,2)
Remaining
start with [1,2]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
[1,2] added to results
Partial: [1, 2]
Found 1: [1,2]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Remaining
check if comb[1] can be incremented
Partial: [1, 2]
Found 1: [1,2]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[1] not max, i=1
Partial: [1, 2]
Found 1: [1,2]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
increment comb[1] to 3
Partial: [1, 3]
Found 1: [1,2]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
no reset needed
Partial: [1, 3]
Found 1: [1,2]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
[1,3] added to results
Partial: [1, 3]
Found 2: [1,2] [1,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Remaining
check if comb[1] can be incremented
Partial: [1, 3]
Found 2: [1,2] [1,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[1] not max, i=1
Partial: [1, 3]
Found 2: [1,2] [1,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
increment comb[1] to 4
Partial: [1, 4]
Found 2: [1,2] [1,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
no reset needed
Partial: [1, 4]
Found 2: [1,2] [1,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
[1,4] added to results
Partial: [1, 4]
Found 3: [1,3] [1,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Remaining
check if comb[1] can be incremented
Partial: [1, 4]
Found 3: [1,3] [1,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[1] max, i=0
Remaining
check if comb[0] can be incremented
Partial: [1, 4]
Found 3: [1,3] [1,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[0] not max, i=0
Partial: [1, 4]
Found 3: [1,3] [1,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
increment comb[0] to 2
Partial: [2, 4]
Found 3: [1,3] [1,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
reset comb[1] to 3
Partial: [2, 3]
Found 3: [1,3] [1,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
[2,3] added to results
Partial: [2, 3]
Found 4: [1,4] [2,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Remaining
check if comb[1] can be incremented
Partial: [2, 3]
Found 4: [1,4] [2,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[1] not max, i=1
Partial: [2, 3]
Found 4: [1,4] [2,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
increment comb[1] to 4
Partial: [2, 4]
Found 4: [1,4] [2,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
no reset needed
Partial: [2, 4]
Found 4: [1,4] [2,3]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
[2,4] added to results
Partial: [2, 4]
Found 5: [2,3] [2,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Remaining
check if comb[1] can be incremented
Partial: [2, 4]
Found 5: [2,3] [2,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[1] max, i=0
Remaining
check if comb[0] can be incremented
Partial: [2, 4]
Found 5: [2,3] [2,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[0] not max, i=0
Partial: [2, 4]
Found 5: [2,3] [2,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
increment comb[0] to 3
Partial: [3, 4]
Found 5: [2,3] [2,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
reset comb[1] to 4
Partial: [3, 4]
Found 5: [2,3] [2,4]
choosingn=4k=2
Call Path (current → root)
combine(4,2)
Tried
[3,4] added to results
Partial: [3, 4]
Found 6: [2,4] [3,4]
pruningn=4k=2
Call Path (current → root)
combine(4,2)
Tried
comb[1] max, i=0comb[0] max, i=-1
Partial: [3, 4]
✂ No element can be incremented further
Found 6: [2,4] [3,4]
Key Takeaways
✓ The algorithm generates combinations in lexicographic order by incrementing the rightmost possible element.
This insight is hard to see from code alone because the increment and reset logic is subtle and intertwined.
✓ Resetting elements to the right of the incremented position ensures combinations remain strictly increasing and valid.
Visualizing this reset clarifies why the algorithm never produces duplicates or invalid sequences.
✓ The stopping condition occurs when no element can be incremented further, signaling completion.
Seeing the decrement of i to -1 and the break condition helps understand how the algorithm knows when to stop.
Practice
(1/5)
1. Consider the following Python code implementing the optimal backtracking with bitmask and memoization approach for the Matchsticks to Square problem. Given the input [1,1,2,2,2], what is the final return value of makesquare([1,1,2,2,2])?
easy
A. True
B. Depends on the order of matchsticks
C. Raises an exception due to index error
D. False
Solution
Step 1: Calculate total and side length
Total = 1+1+2+2+2 = 8, side = 8/4 = 2.
Step 2: Trace backtracking calls
Sorted sticks: [2,2,2,1,1]. The algorithm tries to form sides of length 2. It can form three sides with three 2's, and the last side with two 1's. Hence, returns true.
Final Answer:
Option A -> Option A
Quick Check:
Sum divisible by 4 and sticks fit sides exactly [OK]
Hint: Sum divisible by 4 and sticks fit sides exactly [OK]
Common Mistakes:
Forgetting to sort and prune
Assuming order affects result
Miscounting sides formed
2. Consider the following Python function implementing backtracking with bitmask memoization for partitioning an array into k equal sum subsets. Given nums = [4, 3, 2, 3, 5, 2, 1] and k = 4, what is the final return value of canPartitionKSubsets(nums, k)?
easy
A. True
B. False
C. Raises an exception due to index error
D. Returns None
Solution
Step 1: Calculate total and target sum
Total sum is 4+3+2+3+5+2+1=20, target per subset = 20/4=5.
Step 2: Trace backtracking with sorted nums
Sorted nums: [5,4,3,3,2,2,1]. The algorithm finds subsets: {5}, {4,1}, {3,2}, {3,2} all summing to 5, so returns True.
Final Answer:
Option A -> Option A
Quick Check:
All subsets sum to target, so partition possible [OK]
Hint: Sum divisible by k and backtracking finds valid subsets [OK]
Common Mistakes:
Miscounting sum or target
Assuming no solution due to order
3. The following code attempts to count subsets with sum K using space-optimized DP. Identify the line that contains a subtle bug that can cause incorrect results.
def count_subsets_space_optimized(arr, K):
dp = [0] * (K + 1)
dp[0] = 1
for num in arr:
for j in range(num, K + 1):
dp[j] += dp[j - num]
return dp[K]
medium
A. for j in range(num, K + 1):
B. dp[0] = 1
C. dp = [0] * (K + 1)
D. dp[j] += dp[j - num]
Solution
Step 1: Analyze iteration order
The inner loop iterates forward from num to K, which causes dp[j] to use updated dp values from the same iteration, leading to overcounting.
Step 2: Correct iteration direction
To avoid overcounting, the inner loop must iterate backward from K down to num.
Final Answer:
Option A -> Option A
Quick Check:
Forward iteration in DP causes incorrect counts -> bug in loop range [OK]
Hint: Inner loop must iterate backward to avoid reuse of updated dp values [OK]
Common Mistakes:
Using forward iteration in space-optimized DP
Misunderstanding dp update dependencies
4. Suppose the problem is modified so that letters can be toggled multiple times, allowing reuse of characters in permutations (e.g., toggling the same letter case multiple times in different positions). Which modification to the backtracking approach correctly handles this variant without generating duplicates?
hard
A. Use a visited set to track permutations and skip duplicates after generation
B. Modify recursion to allow toggling letters multiple times but prune branches that revisit the same index
C. Change the algorithm to generate permutations with replacement by increasing recursion depth beyond string length
D. Use iterative BFS with a queue that enqueues toggled strings and avoids revisiting states
Solution
Step 1: Understand reuse variant
Allowing reuse means letters can be toggled multiple times in different positions, increasing state space and possible duplicates.
Step 2: Identify correct approach
Iterative BFS with a queue can track states and avoid revisiting duplicates efficiently, unlike naive recursion which may generate duplicates or infinite loops.
Step 3: Why other options fail
Visited sets after generation are inefficient; pruning recursion by index doesn't prevent reuse; increasing recursion depth breaks problem constraints.
Final Answer:
Option D -> Option D
Quick Check:
BFS with state tracking handles reuse and duplicates [OK]
Hint: Use BFS with visited states to handle reuse [OK]
Common Mistakes:
Trying to prune recursion by index only
Ignoring duplicates generated by reuse
5. Suppose the Matchsticks to Square problem is modified so that each matchstick can be used multiple times (unlimited reuse). Which of the following algorithmic changes correctly adapts the solution to this variant?
hard
A. Use backtracking without bitmask, allowing repeated selection of sticks, and prune when side length exceeded.
B. Keep the bitmask to track used sticks but allow multiple bits per stick to represent reuse counts.
C. Remove the bitmask and use a classic unbounded knapsack DP to check if four sides can be formed from unlimited sticks.
D. Sort sticks and greedily assign sticks to sides repeatedly until all sides are formed.
Solution
Step 1: Understand unlimited reuse impact
Unlimited reuse means the problem becomes an unbounded partition problem, not a subset partition.
Step 2: Identify suitable algorithm
Bitmask tracking used sticks is invalid since sticks can be reused infinitely. Backtracking without bitmask is inefficient. Greedy fails due to partition constraints. Unbounded knapsack DP correctly models unlimited usage.