Input: s ="accca", k =2Output: 3Explanation:
The optimal way is to change `s[2]` to something other than a and c,forexample, b. then it becomes `"acbca"`.Then we perform the operations:1. The longest prefix containing at most 2 distinct characters is`"ac"`, we remove it and `s` becomes `"bca"`.2. Now The longest prefix containing at most 2 distinct characters is`"bc"`, so we remove it and `s` becomes `"a"`.3. Finally, we remove `"a"` and `s` becomes empty, so the procedure ends.Doing the operations, the string is divided into 3 partitions, so the answer
is3.
Input: s ="aabaab", k =3Output: 1Explanation:
Initially `s` contains 2 distinct characters, so whichever character we
change, it will contain at most 3 distinct characters, so the longest prefix
with at most 3 distinct characters would always be all of it, therefore the
answer is1.
Input: s ="xxyz", k =1Output: 4Explanation:
The optimal way is to change `s[0]` or `s[1]` to something other than
characters in`s`,for example, to change `s[0]` to `w`.Then `s` becomes `"wxyz"`, which consists of 4 distinct characters, so as `k`is1, it will divide into 4 partitions.
To maximize the number of partitions, we want to maximize the number of times we can take a prefix with at most k distinct characters. By changing at most one character, we can try all possible single-character changes and simulate the partitioning process for each, taking the maximum result.
classSolution {
public:int maxPartitionsAfterOperations(string s, int k) {
int n = s.size(), ans =0;
auto getPartitions = [&](string t) {
int cnt =0, l =0;
while (l < n) {
unordered_set<char> st;
int r = l;
while (r < n && (st.count(t[r]) || st.size() < k)) {
st.insert(t[r]);
r++;
}
cnt++;
l = r;
}
return cnt;
};
ans = getPartitions(s);
for (int i =0; i < n; ++i) {
for (char c ='a'; c <='z'; ++c) {
if (c == s[i]) continue;
string t = s;
t[i] = c;
ans = max(ans, getPartitions(t));
}
}
return ans;
}
};
classSolution {
publicintmaxPartitionsAfterOperations(String s, int k) {
int n = s.length(), ans = 0;
ans = getPartitions(s, k);
for (int i = 0; i < n; i++) {
for (char c ='a'; c <='z'; c++) {
if (c == s.charAt(i)) continue;
StringBuilder t =new StringBuilder(s);
t.setCharAt(i, c);
ans = Math.max(ans, getPartitions(t.toString(), k));
}
}
return ans;
}
privateintgetPartitions(String s, int k) {
int n = s.length(), cnt = 0, l = 0;
while (l < n) {
Set<Character> st =new HashSet<>();
int r = l;
while (r < n && (st.contains(s.charAt(r)) || st.size() < k)) {
st.add(s.charAt(r));
r++;
}
cnt++;
l = r;
}
return cnt;
}
}
classSolution:
defmaxPartitionsAfterOperations(self, s: str, k: int) -> int:
defget_partitions(t: str) -> int:
n = len(t)
cnt =0 l =0while l < n:
st = set()
r = l
while r < n and (t[r] in st or len(st) < k):
st.add(t[r])
r +=1 cnt +=1 l = r
return cnt
ans = get_partitions(s)
n = len(s)
for i in range(n):
for c in map(chr, range(97, 123)):
if c == s[i]:
continue t = s[:i] + c + s[i+1:]
ans = max(ans, get_partitions(t))
return ans
impl Solution {
pubfnmax_partitions_after_operations(s: String, k: i32) -> i32 {
let n = s.len();
let s = s.as_bytes();
letmut ans = get_partitions(s, k);
for i in0..n {
for c inb'a'..=b'z' {
if c == s[i] { continue; }
letmut t = s.to_vec();
t[i] = c;
ans = ans.max(get_partitions(&t, k));
}
}
ans
}
}
fnget_partitions(s: &[u8], k: i32) -> i32 {
let n = s.len();
letmut cnt =0;
letmut l =0;
while l < n {
letmut st = std::collections::HashSet::new();
letmut r = l;
while r < n && (st.contains(&s[r]) || st.len() < k asusize) {
st.insert(s[r]);
r +=1;
}
cnt +=1;
l = r;
}
cnt
}
The brute-force simulation in Method 1 re-simulates partitioning for many candidate single-character edits and therefore becomes too slow. We can speed this up using dynamic programming. Represent the set of distinct letters in the current active prefix as a 26-bit bitmask; bitwise OR and bit-count operations (mask | (1 << c), Integer.bitCount(mask)) update and measure distinct letters cheaply. The DP state is the triple (index, mask, canChange) and memoizing results avoids revisiting the same subproblems.
Define a recursive helper dp(index, mask, canChange) that returns the maximum number of additional partitions possible starting at index when the active prefix has letters denoted by mask and canChange indicates whether the single allowed character-change is still available.
If index == len(s) return 0 (no more characters → no more partitions).
Let c = s.charAt(index) and compute mask' = mask | (1 << (c - 'a')) and distinct = popcount(mask').
If distinct <= k, we can continue the current prefix: candidate = dp(index+1, mask', canChange).
If distinct > k, the active prefix must end before this character: candidate = 1 + dp(index+1, 1 << (c - 'a'), canChange) (we start a new prefix containing only the current character).
If canChange is true, try substituting the current character by each letter x in [0..25]:
Memoize the computed result for (index, mask, canChange) and return it. The initial call is dp(0, 0, true) + 1 (we add 1 to convert the “additional partitions” count to total partitions).
⏰ Time complexity:O(n * M) – Each DP state (index, mask, flag) is computed once; in the worst case we may explore up to M different masks reachable from each index (practically much smaller than 2^26) and when canChange is true we try up to 26 substitutions.
🧺 Space complexity:O(n * M) – Memoization stores results per reachable (index, mask, flag) state.
We can convert the recursive DP into an iterative, sparse DP that processes the string from left to right. Instead of storing the result for every index, mask, and flag, we keep a sparse map of reachable (mask, flag) states after processing the first i characters and update it when we consume the next character. Each state stores the number of completed partitions so far; after processing all characters we add one for the final active prefix.
Use a sparse map cur mapping a packed key (mask, flag) -> completed_partitions after processing i characters. Pack a key as (mask << 1) | flag where flag is 1 if the single change is still available.
Initialize cur = { (0 << 1) | 1 : 0 } (no letters in active prefix, change still available, 0 completed partitions).
For each index i from 0 to n-1 with character c = s[i]:
Create an empty next map.
For every (mask, flag) in cur with value val:
Let maskUpdated = mask | (1 << (c - 'a')).
If popcount(maskUpdated) <= k, we can extend current prefix: update next[(maskUpdated, flag)] = max(next.get(...), val).
Otherwise we must end the current prefix and start a new one containing c: update next[(1 << (c - 'a'), flag)] = max(..., val + 1).
If flag is 1, try substituting c with every nc in [0..25]:
Compute maskNew = mask | (1 << nc).
If popcount(maskNew) <= k update next[(maskNew, 0)] = max(..., val) else next[(1 << nc, 0)] = max(..., val + 1).
Replace cur with next and continue.
After processing all n characters, the answer is max(cur.values()) + 1 (the final active prefix contributes one partition).
This forward (left-to-right) iterative DP is equivalent to the top-down memoized DP but easier to implement iteratively and to reason about reachable sparse states.
⏰ Time complexity:O(n * S * 26) – For each of the n characters we iterate over the current sparse state set S and (when flag is true) may try up to 26 substitutions; in the worst case S equals the number of reachable masks times two flags.
🧺 Space complexity:O(S) – The sparse maps hold at most S reachable (mask, flag) states at any step.
classSolution {
funmaxPartitionsAfterOperations(s: String, k: Int): Int {
val n = s.length
var cur = HashMap<Int, Int>()
cur[(0 shl 1) or 1] = 0for (i in0 until n) {
val next = HashMap<Int, Int>()
val ch = s[i] - 'a'for ((packed, val) in cur) {
val flag = packed and 1val mask = packed ushr 1val maskUpdated = mask or (1 shl ch)
if (Integer.bitCount(maskUpdated) <= k) {
val np = (maskUpdated shl 1) or flag
next[np] = maxOf(next.getOrDefault(np, Int.MIN_VALUE), val)
} else {
val np = ((1 shl ch) shl 1) or flag
next[np] = maxOf(next.getOrDefault(np, Int.MIN_VALUE), val+ 1) }
if (flag ==1) {
for (nc in0 until 26) {
val maskNew = mask or (1 shl nc)
if (Integer.bitCount(maskNew) <= k) {
val np = (maskNew shl 1) or 0 next[np] = maxOf(next.getOrDefault(np, Int.MIN_VALUE), val)
} else {
val np = ((1 shl nc) shl 1) or 0 next[np] = maxOf(next.getOrDefault(np, Int.MIN_VALUE), val+ 1) }
}
}
}
cur = next
}
var ans = Int.MIN_VALUE
for ((_, v) in cur) ans = maxOf(ans, v)
return ans + 1 }
}