💡 Reconstruction complete when max_index becomes -1.
reconstruct
Reverse result to get final subset
Reverse the result list [2, 1] to get [1, 2], the largest divisible subset in correct order.
💡 Reversing is necessary because reconstruction collected elements backwards.
Line:return result[::-1]
💡 The final answer is the largest divisible subset found by the algorithm.
from typing import List
def largestDivisibleSubset(nums: List[int]) -> List[int]:
if not nums:
return [] # STEP 1: Handle empty input
nums.sort() # STEP 1: Sort input
n = len(nums)
dp = [1] * n # STEP 2: Initialize dp array
prev = [-1] * n # STEP 2: Initialize prev array
max_index = 0 # STEP 2: Track index of largest subset
for i in range(n): # STEP 3: Outer loop over nums
for j in range(i - 1, -1, -1): # STEP 4: Inner loop backwards
if nums[i] % nums[j] == 0: # STEP 5: Check divisibility
if dp[j] + 1 > dp[i]: # STEP 5: Update dp and prev if longer
dp[i] = dp[j] + 1
prev[i] = j
else:
break # STEP 8: Early break if not divisible
if dp[i] > dp[max_index]: # STEP 6 & 10: Update max_index
max_index = i
result = [] # STEP 11: Prepare to reconstruct subset
while max_index >= 0: # STEP 12 & 13: Reconstruct subset
result.append(nums[max_index])
max_index = prev[max_index]
return result[::-1] # STEP 14: Reverse and return
📊
Largest Divisible Subset - Watch the Algorithm Execute, Step by Step
Watching each comparison and update helps you understand how the algorithm efficiently finds the largest divisible subset by building on smaller solutions and pruning unnecessary checks.
Step 1/14
·Active fill★Answer cell
enteringnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
enteringnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Remaining
i=0
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
i=0
Remaining
i=1
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
j=0
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
i=1
Remaining
i=2
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
i=0i=1
Remaining
i=2
pruningnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
j=1
✂ 3 % 2 != 0, early break
backtrackingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
j=1
✂ early break prevents j=0 check
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Tried
i=0i=1i=2
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
choosingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Partial: [2]
backtrackingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Partial: [2, 1]
backtrackingnums=[1,2,3]
Call Path (current → root)
largestDivisibleSubset([1, 2, 3])
Found 1: [1,2]
Key Takeaways
✓ Sorting the input array enables early breaks in the inner loop, significantly reducing unnecessary divisibility checks.
This optimization is subtle and not obvious from code alone; watching the early break in action clarifies its importance.
✓ The dp array stores the length of the largest divisible subset ending at each index, while the prev array stores the predecessor index to reconstruct the subset.
Seeing dp and prev update step-by-step helps understand how dynamic programming builds solutions incrementally.
✓ Reconstruction of the subset by following prev pointers from max_index back to -1 reveals the actual subset, not just its size.
This backtracking step is often overlooked but critical to retrieving the final answer.
Practice
(1/5)
1. Consider the following Python function that counts subsets with sum K using a space-optimized DP approach. What is the output when called with arr = [1, 2, 3, 3] and K = 6?
def count_subsets_space_optimized(arr, K):
dp = [0] * (K + 1)
dp[0] = 1
for num in arr:
for j in range(K, num - 1, -1):
dp[j] += dp[j - num]
return dp[K]
print(count_subsets_space_optimized(arr, K))
easy
A. 5
B. 4
C. 3
D. 6
Solution
Step 1: Initialize dp array
dp = [1,0,0,0,0,0,0] since dp[0]=1 and K=6.
Step 2: Process each number updating dp
After processing 1: dp = [1,1,0,0,0,0,0]
After 2: dp = [1,1,1,1,0,0,0]
After first 3: dp = [1,1,1,2,1,1,1]
After second 3: dp = [1,1,1,3,2,2,3]
Final Answer:
Option A -> Option A
Quick Check:
dp[6] = 5 subsets sum to 6 [OK]
Hint: Trace dp updates backward to avoid double counting [OK]
Common Mistakes:
Forgetting to iterate backward in dp array
Off-by-one errors in dp indices
2. You need to generate all possible subsets of a given set of distinct integers. Which algorithmic approach guarantees enumerating every subset exactly once, including the empty subset, without missing or duplicating any subset?
easy
A. Greedy algorithm that picks elements based on a heuristic to maximize subset size
B. Dynamic programming approach that builds subsets by summing elements to target values
C. Backtracking with include/exclude decisions at each element to explore all subset combinations
D. Sorting the array and then using two pointers to find pairs that form subsets
Solution
Step 1: Understand problem requirements
The problem requires enumerating all subsets, which means exploring all combinations of including or excluding each element.
Step 2: Identify suitable algorithm
Backtracking with include/exclude decisions systematically explores all subsets without duplication or omission, including the empty subset.
Final Answer:
Option C -> Option C
Quick Check:
Backtracking ensures all subsets are generated [OK]
Hint: Include/exclude backtracking covers all subsets [OK]
Common Mistakes:
Confusing subset generation with greedy or DP sum problems
3. What is the time complexity of generating all subsets of an array of size n using bitmask enumeration, and why is the common misconception that it is O(2^n) incorrect?
medium
A. O(n^2) because we iterate over n elements twice
B. O(2^n) because there are 2^n subsets
C. O(n * 2^n) because each subset can have up to n elements and we build each subset by checking n bits
D. O(n) because we process each element once
Solution
Step 1: Count subsets and subset size
There are 2^n subsets, and each subset can have up to n elements.
Step 2: Analyze bitmask enumeration cost
For each of the 2^n masks, we check n bits to build the subset, resulting in O(n * 2^n) time.
Final Answer:
Option C -> Option C
Quick Check:
Time depends on both number of subsets and subset size [OK]
Hint: Each subset requires O(n) to build, total O(n*2^n) [OK]
Common Mistakes:
Ignoring subset size in complexity
Assuming O(2^n) only
Confusing with DP complexities
4. 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.
Trying to track usage with bitmask for unlimited reuse
Using greedy for partitioning
Ignoring pruning in backtracking
5. Suppose the problem changes: now each element in the input array can be chosen multiple times (unlimited reuse), and duplicates still exist. Which modification to the iterative approach correctly generates all unique subsets with duplicates and unlimited reuse?
hard
A. Keep the same code but remove the duplicate skipping condition to allow reuse.
B. Sort the array, and for each element, always start from index 0 when adding new subsets to allow reuse, but skip duplicates at the same recursion level.
C. Sort the array, and for each element, start from the previous start index for duplicates, but allow reuse by appending to all existing subsets including newly added ones in the same iteration.
D. Use backtracking with a visited array to track reuse and skip duplicates by sorting.
Solution
Step 1: Understand unlimited reuse impact
Unlimited reuse means subsets can include the same element multiple times, so the iteration must consider all existing subsets each time.
Step 2: Modify iteration start index
To allow reuse, always start from index 0 when adding new subsets, so elements can be appended multiple times. However, duplicates must still be skipped at the same recursion level to avoid repeated subsets.
Step 3: Confirm skipping duplicates at recursion level
Skipping duplicates at the same recursion level prevents generating identical subsets multiple times despite reuse.
Final Answer:
Option B -> Option B
Quick Check:
Starting from 0 allows reuse; skipping duplicates avoids repeats [OK]
Hint: Start from 0 to allow reuse; skip duplicates at recursion level [OK]
Common Mistakes:
Removing duplicate skipping causes duplicates
Starting from previous start index blocks reuse
Using visited array unnecessarily complicates iterative approach