💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving performance while keeping the recursive structure.
Intuition
Use a dictionary or map keyed by (index, zeros_left, ones_left) to store results of subproblems and reuse them.
Algorithm
- Define a recursive function with parameters index, zeros_left, ones_left.
- Before computing, check if result is in memo; if yes, return it.
- Compute by skipping or including current string if feasible.
- Store the result in memo and return it.
💡 Memoization reduces exponential calls to polynomial by caching repeated states.
Recurrence:f(i, m, n) = max(f(i+1, m, n), 1 + f(i+1, m - zeros_i, n - ones_i)) if m >= zeros_i and n >= ones_i else f(i+1, m, n)
from typing import List
def findMaxForm(strs: List[str], m: int, n: int) -> int:
memo = {}
def count_zeros_ones(s: str) -> (int, int):
zeros = s.count('0')
ones = len(s) - zeros
return zeros, ones
def dfs(i: int, zeros_left: int, ones_left: int) -> int:
if i == len(strs):
return 0
if (i, zeros_left, ones_left) in memo:
return memo[(i, zeros_left, ones_left)]
zeros, ones = count_zeros_ones(strs[i])
res = dfs(i + 1, zeros_left, ones_left) # skip
if zeros <= zeros_left and ones <= ones_left:
res = max(res, 1 + dfs(i + 1, zeros_left - zeros, ones_left - ones))
memo[(i, zeros_left, ones_left)] = res
return res
return dfs(0, m, n)
# Example usage:
if __name__ == '__main__':
strs = ["10", "0001", "111001", "1", "0"]
m, n = 5, 3
print(findMaxForm(strs, m, n)) # Output: 4
Line Notes
memo = {}Cache to store results of subproblems
if (i, zeros_left, ones_left) in memo:Return cached result to avoid recomputation
memo[(i, zeros_left, ones_left)] = resStore computed result for future calls
res = max(res, 1 + dfs(i + 1, zeros_left - zeros, ones_left - ones))Include current string if feasible and update max
import java.util.*;
public class OnesAndZeroesMemo {
private static Map<String, Integer> memo = new HashMap<>();
public static int findMaxForm(String[] strs, int m, int n) {
return dfs(strs, 0, m, n);
}
private static int dfs(String[] strs, int i, int zerosLeft, int onesLeft) {
if (i == strs.length) return 0;
String key = i + "," + zerosLeft + "," + onesLeft;
if (memo.containsKey(key)) return memo.get(key);
int zeros = 0, ones = 0;
for (char c : strs[i].toCharArray()) {
if (c == '0') zeros++;
else ones++;
}
int res = dfs(strs, i + 1, zerosLeft, onesLeft); // skip
if (zeros <= zerosLeft && ones <= onesLeft) {
res = Math.max(res, 1 + dfs(strs, i + 1, zerosLeft - zeros, onesLeft - ones));
}
memo.put(key, res);
return res;
}
public static void main(String[] args) {
String[] strs = {"10", "0001", "111001", "1", "0"};
int m = 5, n = 3;
System.out.println(findMaxForm(strs, m, n)); // 4
}
}
Line Notes
private static Map<String, Integer> memo = new HashMap<>();Memoization cache keyed by state string
if (memo.containsKey(key)) return memo.get(key);Return cached result if available
memo.put(key, res);Store computed result for reuse
res = Math.max(res, 1 + dfs(strs, i + 1, zerosLeft - zeros, onesLeft - ones));Include current string if possible
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;
string makeKey(int i, int zerosLeft, int onesLeft) {
return to_string(i) + "," + to_string(zerosLeft) + "," + to_string(onesLeft);
}
int dfs(const vector<string>& strs, int i, int zerosLeft, int onesLeft, unordered_map<string,int>& memo) {
if (i == strs.size()) return 0;
string key = makeKey(i, zerosLeft, onesLeft);
if (memo.count(key)) return memo[key];
int zeros = 0, ones = 0;
for (char c : strs[i]) {
if (c == '0') zeros++;
else ones++;
}
int res = dfs(strs, i + 1, zerosLeft, onesLeft, memo); // skip
if (zeros <= zerosLeft && ones <= onesLeft) {
res = max(res, 1 + dfs(strs, i + 1, zerosLeft - zeros, onesLeft - ones, memo));
}
memo[key] = res;
return res;
}
int findMaxForm(vector<string>& strs, int m, int n) {
unordered_map<string,int> memo;
return dfs(strs, 0, m, n, memo);
}
int main() {
vector<string> strs = {"10", "0001", "111001", "1", "0"};
int m = 5, n = 3;
cout << findMaxForm(strs, m, n) << endl; // 4
return 0;
}
Line Notes
unordered_map<string,int>& memoMemoization cache passed by reference
if (memo.count(key)) return memo[key];Return cached result if present
memo[key] = res;Store computed result for reuse
res = max(res, 1 + dfs(strs, i + 1, zerosLeft - zeros, onesLeft - ones, memo));Include current string if feasible
function findMaxForm(strs, m, n) {
const memo = new Map();
function countZerosOnes(s) {
let zeros = 0, ones = 0;
for (let ch of s) {
if (ch === '0') zeros++;
else ones++;
}
return [zeros, ones];
}
function dfs(i, zerosLeft, onesLeft) {
if (i === strs.length) return 0;
const key = i + ',' + zerosLeft + ',' + onesLeft;
if (memo.has(key)) return memo.get(key);
const [zeros, ones] = countZerosOnes(strs[i]);
let res = dfs(i + 1, zerosLeft, onesLeft); // skip
if (zeros <= zerosLeft && ones <= onesLeft) {
res = Math.max(res, 1 + dfs(i + 1, zerosLeft - zeros, onesLeft - ones));
}
memo.set(key, res);
return res;
}
return dfs(0, m, n);
}
// Example usage:
const strs = ["10", "0001", "111001", "1", "0"];
const m = 5, n = 3;
console.log(findMaxForm(strs, m, n)); // 4
Line Notes
const memo = new Map();Memoization cache to store computed states
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, res);Store computed result for reuse
res = Math.max(res, 1 + dfs(i + 1, zerosLeft - zeros, onesLeft - ones));Include current string if feasible
TimeO(n * m * n)
SpaceO(n * m * n)
Memoization reduces calls to unique states bounded by number of strings and zero/one budgets.
💡 For n=600, m=100, n=100, this is feasible but still expensive.
Interview Verdict: Accepted
Memoization makes the solution efficient enough for interview constraints and is a natural step from brute force.