Return dp[5][3] which is the maximum number of strings that can be formed with 5 zeros and 3 ones.
💡 The final dp cell contains the answer to the problem.
Line:return dp[m][n]
💡 The maximum subset size is 4, as dp[5][3] = 4 after all updates.
from typing import List
def findMaxForm(strs: List[str], m: int, n: int) -> int:
dp = [[0] * (n + 1) for _ in range(m + 1)] # STEP 1
for idx, s in enumerate(strs): # STEP 2,6,10,12,16
zeros = s.count('0') # STEP 2,6,10,12,16
ones = len(s) - zeros # STEP 2,6,10,12,16
for i in range(m, zeros - 1, -1): # STEP 3-5,7-9,13-15,17-19
for j in range(n, ones - 1, -1):
dp[i][j] = max(dp[i][j], 1 + dp[i - zeros][j - ones]) # STEP 3-5,7-9,13-15,17-19
return dp[m][n] # STEP 20
if __name__ == '__main__':
strs = ["10", "0001", "111001", "1", "0"]
m, n = 5, 3
print(findMaxForm(strs, m, n)) # Output: 4
📊
Ones and Zeroes (2D Knapsack) - Watch the Algorithm Execute, Step by Step
Watching the dp table fill in real time reveals how the algorithm balances the two constraints and builds up the solution incrementally.
Step 1/20
·Active fill★Answer cell
Initializing dp array
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
0
0
0
i=4
0
0
0
0
i=5
0
0
0
0
initialized to 0
Item 0 - wt:1 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
0
0
0
i=4
0
0
0
0
i=5
0
0
0
0
Item 0 - wt:1 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
0
0
0
i=4
0
0
0
0
i=5
0
0
0
1
updated
Item 0 - wt:1 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
0
0
0
i=4
0
0
1
0
i=5
0
0
0
1
updated
Item 0 - wt:1 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
updated
Item 1 - wt:3 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
Item 1 - wt:3 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
no change
Item 1 - wt:3 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
no change
Item 1 - wt:3 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
no change
Item 2 - wt:2 val:4
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
Item 2 - wt:2 val:4
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
Item 3 - wt:0 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
Item 3 - wt:0 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
0
1
no change
Item 3 - wt:0 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
0
1
1
updated
Item 3 - wt:0 val:1
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
1
1
1
updated
Item 4 - wt:1 val:0
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
1
1
1
Item 4 - wt:1 val:0
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
0
i=5
0
1
1
1
no change
Item 4 - wt:1 val:0
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
0
i=4
0
0
1
1
i=5
0
1
1
1
updated
Item 4 - wt:1 val:0
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
1
i=4
0
0
1
1
i=5
0
1
1
1
updated
Initializing dp array
i\w
0
1
2
3
i=0
0
0
0
0
i=1
0
0
0
0
i=2
0
0
0
0
i=3
0
1
0
1
i=4
0
0
1
1
i=5
0
1
1
4
final answer
Key Takeaways
✓ The dp table incrementally builds the maximum subset size respecting two constraints simultaneously.
This insight is hard to see from code alone because the two-dimensional resource constraints and their interaction are subtle.
✓ The dp table is filled in reverse order of zeros and ones to avoid overwriting values needed for future computations.
Watching the fill order visually clarifies why the loops go backwards, which is not obvious from code.
✓ Strings that require more zeros or ones than available are pruned automatically, leaving dp unchanged for those cases.
Seeing no updates for such strings helps understand how resource constraints prune the search space.
Practice
(1/5)
1. Consider the following code snippet implementing the minimum cost for tickets problem. What is the value of dp[0] after the loop completes for the input days = [1,4,6] and costs = [2,7,15]?
2. The following code attempts to solve the integer break problem using bottom-up DP. Which line contains a subtle bug that can cause incorrect results on some inputs?
def integer_break(n):
dp = [0] * n
dp[1] = 1
for i in range(2, n + 1):
max_product = 0
for j in range(1, i):
max_product = max(max_product, max(j, dp[j]) * max(i - j, dp[i - j]))
dp[i] = max_product
return dp[n]
medium
A. Line 3: dp[1] = 1 base case initialization
B. Line 2: dp array size is n instead of n+1
C. Line 5: Outer loop range from 2 to n+1
D. Line 7: Using max(j, dp[j]) instead of just dp[j]
Solution
Step 1: Check dp array size
dp is initialized with size n, but indices up to n are accessed (dp[i], dp[i-j]) which requires size n+1.
Step 2: Consequences of wrong size
Accessing dp[n] or dp[i-j] when i=n causes index out of range or incorrect results.
Final Answer:
Option B -> Option B
Quick Check:
dp array must be size n+1 to safely index up to n [OK]
Hint: Check array size matches max index accessed [OK]
Common Mistakes:
Forgetting dp size off-by-one
Misplacing base case initialization
3. What is the time complexity of the space-optimized bottom-up dynamic programming solution for counting the number of ways to make change with n coins and amount W?
medium
A. O(n * W) because for each coin we iterate over all amounts up to W
B. O(n + W) because we iterate over coins and amounts separately
C. O(W^n) due to nested loops over coins and amounts
D. O(n * W) plus O(W) auxiliary space for the dp array
Solution
Step 1: Identify loops and their ranges
Outer loop runs n times (coins), inner loop runs up to W (amount), so time is O(n * W).
Step 2: Consider space usage
DP array size is W+1, so auxiliary space is O(W). Total complexity includes both time and space, but time complexity is the main focus here.
Final Answer:
Option A -> Option A
Quick Check:
Time O(n*W) matches DP approach [OK]
Hint: Time is product of coins and amount; space is dp array size [OK]
Common Mistakes:
Confusing time with space complexity
Forgetting auxiliary space for dp array
Mistaking exponential complexity for DP
4. The following code attempts to solve the Partition to K Equal Sum Subsets problem using DP with bitmask tabulation. Identify the line containing the subtle bug that can cause incorrect results or infinite loops.
def canPartitionKSubsets(nums, k):
total = sum(nums)
if total % k != 0:
return False
target = total // k
n = len(nums)
nums.sort()
if nums[-1] > target:
return False
dp = [-1] * (1 << n)
dp[0] = 0
for mask in range(1 << n):
if dp[mask] == -1:
continue
for i in range(n):
if (mask & (1 << i)) == 0 and dp[mask] + nums[i] <= target:
next_mask = mask | (1 << i)
dp[next_mask] = (dp[mask] + nums[i]) % target
return dp[(1 << n) - 1] == 0
medium
A. Line: dp = [-1] * (1 << n)
B. Line: if dp[next_mask] == -1: (missing in this code)
C. Line: nums.sort()
D. Line: dp[next_mask] = (dp[mask] + nums[i]) % target
Solution
Step 1: Identify missing condition
The code lacks a check if dp[next_mask] is already set, so it overwrites states, causing incorrect results.
Step 2: Pinpoint the buggy line
The line assigning dp[next_mask] unconditionally overwrites previous valid states, breaking memoization.
Final Answer:
Option D -> Option D
Quick Check:
Adding "if dp[next_mask] == -1:" before assignment fixes bug [OK]
Hint: Always check if dp state is unset before assignment [OK]
Common Mistakes:
Overwriting dp states without checking leads to incorrect answers
Not pruning symmetric states increases runtime
5. Suppose the problem is modified so that each element in the array can be used multiple times (unlimited reuse) to form k equal sum subsets. Which of the following modifications to the DP with bitmask tabulation approach correctly adapts to this change?
hard
A. Use backtracking with memoization on subsets but do not use bitmasking since reuse breaks subset uniqueness
B. Keep the same bitmask DP but allow setting dp[next_mask] multiple times without restriction
C. Replace bitmask DP with a classic unbounded knapsack DP that tracks sums without bitmasking
D. Modify the bitmask DP to reset dp states after each full subset sum is reached to allow reuse
Solution
Step 1: Understand reuse impact
Allowing unlimited reuse breaks the uniqueness assumption of subsets represented by bitmasks.