🧠
Brute Force (Pure Recursion)
💡 Starting with brute force helps understand the problem's recursive structure and why naive solutions are inefficient.
Intuition
Try all possible partitions of the substring and recursively compute the minimum turns needed for each part, then combine results.
Algorithm
- Define a recursive function that returns minimum turns for substring s[i..j].
- If i > j, return 0 (empty substring).
- Initialize result as 1 + recursive call for s[i+1..j] (printing s[i] separately).
- Try all splits k between i+1 and j, if s[k] == s[i], combine results to reduce turns.
- Return the minimum turns found.
💡 The recursion explores all partitions, which is conceptually simple but leads to repeated work and exponential time.
Recurrence:dp(i,j) = 1 + dp(i+1,j) if no merge; else min over k where s[k] == s[i] of dp(i,k-1) + dp(k,j)
def strangePrinter(s: str) -> int:
n = len(s)
from functools import lru_cache
@lru_cache(None)
def dfs(i, j):
if i > j:
return 0
res = 1 + dfs(i + 1, j)
for k in range(i + 1, j + 1):
if s[k] == s[i]:
res = min(res, dfs(i, k - 1) + dfs(k, j))
return res
return dfs(0, n - 1)
# Driver code
if __name__ == '__main__':
print(strangePrinter("aaabbb")) # Output: 2
print(strangePrinter("aba")) # Output: 2
Line Notes
def strangePrinter(s: str) -> int:Defines main function for the problem to compute minimum turns
from functools import lru_cacheEnables memoization to avoid recomputation of overlapping subproblems
if i > j:Base case: empty substring requires 0 turns
res = 1 + dfs(i + 1, j)Print s[i] separately, then print the rest substring
if s[k] == s[i]:Try to merge printing s[i] with s[k] to reduce total turns
res = min(res, dfs(i, k - 1) + dfs(k, j))Combine results of left and right partitions after merging
return dfs(0, n - 1)Start recursion for entire string
import java.util.*;
public class StrangePrinter {
public int strangePrinter(String s) {
int n = s.length();
int[][] memo = new int[n][n];
for (int[] row : memo) Arrays.fill(row, 0);
return dfs(s, 0, n - 1, memo);
}
private int dfs(String s, int i, int j, int[][] memo) {
if (i > j) return 0;
if (memo[i][j] != 0) return memo[i][j];
int res = 1 + dfs(s, i + 1, j, memo);
for (int k = i + 1; k <= j; k++) {
if (s.charAt(k) == s.charAt(i)) {
res = Math.min(res, dfs(s, i, k - 1, memo) + dfs(s, k, j, memo));
}
}
memo[i][j] = res;
return res;
}
public static void main(String[] args) {
StrangePrinter sp = new StrangePrinter();
System.out.println(sp.strangePrinter("aaabbb")); // 2
System.out.println(sp.strangePrinter("aba")); // 2
}
}
Line Notes
int[][] memo = new int[n][n];Memo table to store results for substrings to avoid recomputation
if (i > j) return 0;Base case for empty substring requiring zero turns
if (memo[i][j] != 0) return memo[i][j];Return cached result if available to save time
int res = 1 + dfs(s, i + 1, j, memo);Print s[i] separately plus the rest substring
if (s.charAt(k) == s.charAt(i))Try merging print operations to reduce total turns
res = Math.min(res, dfs(s, i, k - 1, memo) + dfs(s, k, j, memo));Combine results after merging
memo[i][j] = res;Store computed result for reuse
#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;
class Solution {
public:
int strangePrinter(string s) {
int n = s.size();
vector<vector<int>> memo(n, vector<int>(n, 0));
return dfs(s, 0, n - 1, memo);
}
int dfs(const string& s, int i, int j, vector<vector<int>>& memo) {
if (i > j) return 0;
if (memo[i][j] != 0) return memo[i][j];
int res = 1 + dfs(s, i + 1, j, memo);
for (int k = i + 1; k <= j; ++k) {
if (s[k] == s[i]) {
res = min(res, dfs(s, i, k - 1, memo) + dfs(s, k, j, memo));
}
}
memo[i][j] = res;
return res;
}
};
int main() {
Solution sol;
cout << sol.strangePrinter("aaabbb") << endl; // 2
cout << sol.strangePrinter("aba") << endl; // 2
return 0;
}
Line Notes
vector<vector<int>> memo(n, vector<int>(n, 0));Memo table initialization to cache results
if (i > j) return 0;Base case for empty substring requiring zero turns
if (memo[i][j] != 0) return memo[i][j];Return cached result if computed
int res = 1 + dfs(s, i + 1, j, memo);Print s[i] separately plus rest substring
if (s[k] == s[i])Try merging print operations to reduce turns
res = min(res, dfs(s, i, k - 1, memo) + dfs(s, k, j, memo));Combine results after merging
memo[i][j] = res;Store result for reuse
var strangePrinter = function(s) {
const n = s.length;
const memo = Array.from({length: n}, () => Array(n).fill(0));
function dfs(i, j) {
if (i > j) return 0;
if (memo[i][j] !== 0) return memo[i][j];
let res = 1 + dfs(i + 1, j);
for (let k = i + 1; k <= j; k++) {
if (s[k] === s[i]) {
res = Math.min(res, dfs(i, k - 1) + dfs(k, j));
}
}
memo[i][j] = res;
return res;
}
return dfs(0, n - 1);
};
// Test cases
console.log(strangePrinter("aaabbb")); // 2
console.log(strangePrinter("aba")); // 2
Line Notes
const memo = Array.from({length: n}, () => Array(n).fill(0));Memo table to cache results for substrings
if (i > j) return 0;Base case for empty substring requiring zero turns
if (memo[i][j] !== 0) return memo[i][j];Return cached result if available
let res = 1 + dfs(i + 1, j);Print s[i] separately plus rest substring
if (s[k] === s[i])Try merging print operations to reduce turns
res = Math.min(res, dfs(i, k - 1) + dfs(k, j));Combine results after merging
memo[i][j] = res;Store computed result
There are O(n^2) states and for each state we try O(n) splits, leading to O(n^3) time. Memoization reduces repeated calls.
💡 For n=100, this means about 1 million operations, which is borderline but acceptable with memoization.
Interview Verdict: Accepted with memoization, but slow for large inputs
This approach is a good starting point but inefficient; it shows the problem structure clearly.