Bird
Raised Fist0
Interview Prepdp-knapsackmediumAmazonGoogleFacebook

Ones and Zeroes (2D Knapsack)

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Steps
setup

Initialize DP Table

Create a 2D dp array of size (m+1) x (n+1) filled with zeros, representing no strings chosen yet.

💡 Initialization sets the base case: with zero strings, the maximum subset size is zero regardless of zeros and ones available.
Line:dp = [[0] * (n + 1) for _ in range(m + 1)]
💡 The dp table starts empty, ready to be filled as strings are processed.
📊
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 fillAnswer cell
Initializing dp array
i\w0123
i=00000
i=10000
i=20000
i=30000
i=40000
i=50000
initialized to 0
Item 0 - wt:1 val:1
i\w0123
i=00000
i=10000
i=20000
i=30000
i=40000
i=50000
Item 0 - wt:1 val:1
i\w0123
i=00000
i=10000
i=20000
i=30000
i=40000
i=50001
updated
Item 0 - wt:1 val:1
i\w0123
i=00000
i=10000
i=20000
i=30000
i=40010
i=50001
updated
Item 0 - wt:1 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
updated
Item 1 - wt:3 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
Item 1 - wt:3 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
no change
Item 1 - wt:3 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
no change
Item 1 - wt:3 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
no change
Item 2 - wt:2 val:4
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
Item 2 - wt:2 val:4
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
Item 3 - wt:0 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
Item 3 - wt:0 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50001
no change
Item 3 - wt:0 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50011
updated
Item 3 - wt:0 val:1
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50111
updated
Item 4 - wt:1 val:0
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50111
Item 4 - wt:1 val:0
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40010
i=50111
no change
Item 4 - wt:1 val:0
i\w0123
i=00000
i=10000
i=20000
i=30100
i=40011
i=50111
updated
Item 4 - wt:1 val:0
i\w0123
i=00000
i=10000
i=20000
i=30101
i=40011
i=50111
updated
Initializing dp array
i\w0123
i=00000
i=10000
i=20000
i=30101
i=40011
i=50114
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]?
easy
A. 6
B. 7
C. 9
D. 4

Solution

  1. Step 1: Trace dp from the end

    dp[3] = 0 (base case). For i=2 (day=6): - 1-day ticket: cost=2 + dp[3]=2 - 7-day ticket: cost=7 + dp[3]=7 - 30-day ticket: cost=15 + dp[3]=15 Minimum = 2 -> dp[2]=2
  2. Step 2: Calculate dp[1] and dp[0]

    i=1 (day=4): - 1-day: cost=2 + dp[2]=4 - 7-day: cost=7 + dp[3]=7 - 30-day: cost=15 + dp[3]=15 Minimum=4 -> dp[1]=4 i=0 (day=1): - 1-day: cost=2 + dp[1]=6 - 7-day: cost=7 + dp[3]=7 - 30-day: cost=15 + dp[3]=15 Minimum=6 -> dp[0]=6
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    dp[0] correctly computed as 6 [OK]
Hint: Trace dp from end to start carefully [OK]
Common Mistakes:
  • Off-by-one in dp indexing
  • Miscomputing next_i with bisect
  • Confusing costs and dp sums
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

  1. 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.
  2. Step 2: Consequences of wrong size

    Accessing dp[n] or dp[i-j] when i=n causes index out of range or incorrect results.
  3. Final Answer:

    Option B -> Option B
  4. 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

  1. 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).
  2. 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.
  3. Final Answer:

    Option A -> Option A
  4. 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

  1. Step 1: Identify missing condition

    The code lacks a check if dp[next_mask] is already set, so it overwrites states, causing incorrect results.
  2. Step 2: Pinpoint the buggy line

    The line assigning dp[next_mask] unconditionally overwrites previous valid states, breaking memoization.
  3. Final Answer:

    Option D -> Option D
  4. 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

  1. Step 1: Understand reuse impact

    Allowing unlimited reuse breaks the uniqueness assumption of subsets represented by bitmasks.
  2. Step 2: Identify suitable DP approach

    Classic unbounded knapsack DP tracks sums without bitmasking, correctly handling reuse.
  3. Step 3: Evaluate other options

    Bitmask DP relies on unique element usage; modifying it without removing bitmasking leads to incorrect states.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Unbounded knapsack DP handles reuse correctly [OK]
Hint: Bitmask DP assumes unique usage; reuse needs unbounded knapsack DP [OK]
Common Mistakes:
  • Trying to reuse bitmask DP without removing uniqueness assumption
  • Ignoring that reuse breaks subset partitioning logic