💡 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 solution for Combination Sum II. What is the final returned list when calling combinationSum2([1,2,2], 4)?
easy
A. [[1,2,2]]
B. [[1,2],[2]]
C. [[1,2],[2,2]]
D. [[1,2],[2,2],[1,2,2]]
Solution
Step 1: Sort candidates and start backtracking
Sorted candidates: [1,2,2]. Start from index 0 with target=4.
Step 2: Explore combinations skipping duplicates at same level
Try 1 -> target=3, then try 2 at next index -> target=1, next 2 is duplicate at same level skipped. No further candidates fit. Backtrack and try 2 at index 1 -> target=2, next 2 at index 2 -> target=0, add [2,2]. Both [1,2,2] and [2,2] are valid unique combinations.
Final Answer:
Option C -> Option C
Quick Check:
Both [1,2,2] and [2,2] sum to 4 without reuse and duplicates skipped [OK]
2. You need to generate all possible subsets of a given set of distinct integers. Which approach guarantees generating every subset exactly once with a time complexity proportional to the number of subsets times the size of each subset?
easy
A. Greedy algorithm that picks elements based on their value order
B. Enumerate all bitmasks from 0 to 2^n - 1, selecting elements where bits are set
C. Dynamic programming to count subsets without generating them
D. Sorting the array and using two pointers to find pairs
Solution
Step 1: Understand the problem
We want to generate all subsets of a set, which are 2^n in number.
Step 2: Identify the approach that enumerates all subsets
Bitmask enumeration from 0 to 2^n - 1 maps each bit to an element's inclusion, guaranteeing all subsets exactly once.
Final Answer:
Option B -> Option B
Quick Check:
Bitmask enumeration covers all subsets systematically [OK]
Hint: Bitmask from 0 to 2^n-1 enumerates all subsets [OK]
Common Mistakes:
Thinking greedy or sorting can generate all subsets efficiently
3. What is the worst-case time complexity of the optimized dynamic programming solution for the Largest Divisible Subset problem, where n is the number of elements in the input array?
medium
A. O(n^2) due to nested loops checking divisibility pairs
B. O(2^n) because all subsets are considered
C. O(n^3) because of triple nested loops for subset checks
D. O(n log n) due to sorting and linear DP
Solution
Step 1: Identify main operations
Sorting takes O(n log n). The DP uses two nested loops: outer loop over n elements, inner loop up to i elements.
Step 2: Analyze nested loops
Each pair (i,j) is checked once, resulting in O(n^2) divisibility checks. Early break may speed up in practice but worst case remains O(n^2).
Final Answer:
Option A -> Option A
Quick Check:
DP nested loops dominate complexity [OK]
Hint: DP nested loops -> O(n^2) worst case [OK]
Common Mistakes:
Confusing sorting time as dominant (O(n log n))
Assuming triple nested loops due to subset checks
Thinking brute force complexity applies to DP
4. The following code attempts to find the largest divisible subset but contains a subtle bug. Identify the line with the bug.
def largestDivisibleSubset(nums):
if not nums:
return []
n = len(nums)
dp = [1] * n
prev = [-1] * n
max_index = 0
for i in range(n):
for j in range(i - 1, -1, -1):
if nums[j] % nums[i] == 0:
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1
prev[i] = j
else:
break
if dp[i] > dp[max_index]:
max_index = i
result = []
while max_index >= 0:
result.append(nums[max_index])
max_index = prev[max_index]
return result[::-1]
medium
A. Line with 'result.append(nums[max_index])'
B. Line with 'dp = [1] * n'
C. Line with 'max_index = 0'
D. Line with 'if nums[j] % nums[i] == 0:'
Solution
Step 1: Check divisibility condition
The condition should be nums[i] % nums[j] == 0 because we want to check if current number is divisible by a smaller number.
Step 2: Understand impact of bug
Using nums[j] % nums[i] == 0 reverses the divisibility logic, causing incorrect dp updates and wrong subsets.
Final Answer:
Option D -> Option D
Quick Check:
Correct divisibility check is crucial for correctness [OK]
Hint: Divisibility check direction matters [OK]
Common Mistakes:
Swapping divisor and dividend in modulo check
Not sorting input before DP
Incorrect subset reconstruction
5. What is the time complexity of the optimal backtracking solution for letter case permutation on a string of length n with k letters (non-digit characters)?
medium
A. O(2^k * n) because only letters toggle case and each permutation requires joining n characters
B. O(2^k * k) because only letters are toggled and digits are skipped
C. O(n * 2^n) because each character doubles the permutations
D. O(n^2) because of nested recursion and string concatenation
Solution
Step 1: Identify branching factor
Only letters (k of them) cause branching, each with 2 choices, so total permutations are 2^k.
Step 2: Calculate work per permutation
Each permutation requires joining n characters to form a string, so O(n) per permutation.
Final Answer:
Option A -> Option A
Quick Check:
Time is exponential in letters, linear in string length [OK]
Hint: Complexity depends on letters, not total length [OK]