Maximize the Number of Partitions After Operations
Problem
You are given a string s and an integer k.
First, you are allowed to change at most one index in s to another lowercase English letter.
After that, do the following partitioning operation until s is empty :
- Choose the longest prefix of
scontaining at mostkdistinct characters. - Delete the prefix from
sand increase the number of partitions by one. The remaining characters (if any) insmaintain their initial order.
Return an integer denoting the maximum number of resulting partitions after the operations by optimally choosing at most one index to change.
Examples
Example 1
Input: s = "accca", k = 2
Output: 3
Explanation:
The optimal way is to change `s[2]` to something other than a and c, for
example, 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
is 3.
Example 2
Input: s = "aabaab", k = 3
Output: 1
Explanation:
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 is 1.
Example 3
Input: s = "xxyz", k = 1
Output: 4
Explanation:
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`
is 1, it will divide into 4 partitions.
Constraints
1 <= s.length <= 10^4sconsists only of lowercase English letters.1 <= k <= 26
Solution
Method 1 – Greedy Partitioning with One Change Simulation
Intuition
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.
Approach
- For each index in
s, try changing it to every possible lowercase letter (other than the current one). - For each modified string, simulate the partitioning process:
- Start from the beginning, greedily take the longest prefix with at most
kdistinct characters, remove it, and repeat. - Count the number of partitions.
- Start from the beginning, greedily take the longest prefix with at most
- Also consider the case where no change is made.
- Return the maximum number of partitions found.
Code
C++
class Solution {
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;
}
};
Java
class Solution {
public int maxPartitionsAfterOperations(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;
}
private int getPartitions(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;
}
}
Python
class Solution:
def maxPartitionsAfterOperations(self, s: str, k: int) -> int:
def get_partitions(t: str) -> int:
n = len(t)
cnt = 0
l = 0
while 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
Rust
impl Solution {
pub fn max_partitions_after_operations(s: String, k: i32) -> i32 {
let n = s.len();
let s = s.as_bytes();
let mut ans = get_partitions(s, k);
for i in 0..n {
for c in b'a'..=b'z' {
if c == s[i] { continue; }
let mut t = s.to_vec();
t[i] = c;
ans = ans.max(get_partitions(&t, k));
}
}
ans
}
}
fn get_partitions(s: &[u8], k: i32) -> i32 {
let n = s.len();
let mut cnt = 0;
let mut l = 0;
while l < n {
let mut st = std::collections::HashSet::new();
let mut r = l;
while r < n && (st.contains(&s[r]) || st.len() < k as usize) {
st.insert(s[r]);
r += 1;
}
cnt += 1;
l = r;
}
cnt
}
TypeScript
function maxPartitionsAfterOperations(s: string, k: number): number {
function getPartitions(t: string): number {
let n = t.length, cnt = 0, l = 0;
while (l < n) {
const st = new Set<string>();
let r = l;
while (r < n && (st.has(t[r]) || st.size < k)) {
st.add(t[r]);
r++;
}
cnt++;
l = r;
}
return cnt;
}
let ans = getPartitions(s);
for (let i = 0; i < s.length; i++) {
for (let c = 97; c <= 122; c++) {
const ch = String.fromCharCode(c);
if (ch === s[i]) continue;
const t = s.slice(0, i) + ch + s.slice(i+1);
ans = Math.max(ans, getPartitions(t));
}
}
return ans;
};
Complexity
- ⏰ Time complexity:
O(n * 26 * n)— for each index and each letter, we simulate the partitioning process. - 🧺 Space complexity:
O(n)— for temporary strings and sets.
Method 2 — DP with bitmask and memoization (efficient)
Intuition
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.
Approach
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)return0(no more characters → no more partitions). - Let
c = s.charAt(index)and computemask' = mask | (1 << (c - 'a'))anddistinct = 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
- If
canChangeistrue, try substituting the current character by each letterxin[0..25]:- Compute
maskX = mask | (1 << x)anddistinctX = popcount(maskX). - If
distinctX <= k, considerdp(index+1, maskX, false). - Otherwise consider
1 + dp(index+1, 1 << x, false). - Keep the maximum over all choices.
- Compute
- Memoize the computed result for
(index, mask, canChange)and return it. The initial call isdp(0, 0, true) + 1(we add1to convert the "additional partitions" count to total partitions).
Complexity
- ⏰ Time complexity:
O(n * M)– Each DP state(index, mask, flag)is computed once; in the worst case we may explore up toMdifferent masks reachable from each index (practically much smaller than2^26) and whencanChangeis true we try up to 26 substitutions. - 🧺 Space complexity:
O(n * M)– Memoization stores results per reachable(index, mask, flag)state.
Code
Implementations below follow the same Solution class and maxPartitionsAfterOperations signature.
C++
using namespace std;
class Solution {
public:
unordered_map<long long, int> memo;
string s;
int k;
int maxPartitionsAfterOperations(string s_, int k_) {
s = move(s_);
k = k_;
memo.clear();
return dp(0, 0, true) + 1;
}
private:
int dp(int index, int mask, bool canChange) {
if (index == (int)s.size()) return 0;
long long key = (((long long)index) << 27) | (((long long)mask) << 1) | (canChange ? 1LL : 0LL);
auto it = memo.find(key);
if (it != memo.end()) return it->second;
int ch = s[index] - 'a';
int maskUpdated = mask | (1 << ch);
int distinct = __builtin_popcount(maskUpdated);
int res;
if (distinct > k) {
res = 1 + dp(index + 1, 1 << ch, canChange);
} else {
res = dp(index + 1, maskUpdated, canChange);
}
if (canChange) {
for (int nc = 0; nc < 26; ++nc) {
int maskNew = mask | (1 << nc);
int d2 = __builtin_popcount(maskNew);
if (d2 > k) {
res = max(res, 1 + dp(index + 1, 1 << nc, false));
} else {
res = max(res, dp(index + 1, maskNew, false));
}
}
}
memo[key] = res;
return res;
}
};
Go
package main
type Solution struct {
memo map[int]int64 // use int64 keys stored as map values
s string
k int
}
func (sol *Solution) maxPartitionsAfterOperations(s string, k int) int {
sol.s = s
sol.k = k
sol.memo = make(map[int]int64)
return sol.dp(0, 0, true) + 1
}
func (sol *Solution) dp(index int, mask int, canChange bool) int {
if index == len(sol.s) {
return 0
}
key := (index << 27) | (mask << 1)
if canChange {
key |= 1
}
if val, ok := sol.memo[key]; ok {
return int(val)
}
ch := int(sol.s[index]-'a')
maskUpdated := mask | (1 << ch)
distinct := bitsOn(maskUpdated)
var res int
if distinct > sol.k {
res = 1 + sol.dp(index+1, 1<<ch, canChange)
} else {
res = sol.dp(index+1, maskUpdated, canChange)
}
if canChange {
for nc := 0; nc < 26; nc++ {
maskNew := mask | (1 << nc)
d2 := bitsOn(maskNew)
if d2 > sol.k {
res = max(res, 1+sol.dp(index+1, 1<<nc, false))
} else {
res = max(res, sol.dp(index+1, maskNew, false))
}
}
}
sol.memo[key] = int64(res)
return res
}
// helper functions
func bitsOn(x int) int {
cnt := 0
for x != 0 {
x &= x - 1
cnt++
}
return cnt
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
Java
class Solution {
private final HashMap<Long, Integer> memo = new HashMap<>();
private String s;
private int k;
public int maxPartitionsAfterOperations(String s, int k) {
this.s = s;
this.k = k;
memo.clear();
return dp(0, 0, true) + 1;
}
private int dp(int index, int mask, boolean canChange) {
if (index == s.length()) return 0;
long key = (((long) index) << 27) | (((long) mask) << 1) | (canChange ? 1L : 0L);
if (memo.containsKey(key)) return memo.get(key);
int ch = s.charAt(index) - 'a';
int maskUpdated = mask | (1 << ch);
int distinct = Integer.bitCount(maskUpdated);
int res;
if (distinct > k) {
res = 1 + dp(index + 1, 1 << ch, canChange);
} else {
res = dp(index + 1, maskUpdated, canChange);
}
if (canChange) {
for (int nc = 0; nc < 26; nc++) {
int maskNew = mask | (1 << nc);
int d2 = Integer.bitCount(maskNew);
if (d2 > k) {
res = Math.max(res, 1 + dp(index + 1, 1 << nc, false));
} else {
res = Math.max(res, dp(index + 1, maskNew, false));
}
}
}
memo.put(key, res);
return res;
}
}
Kotlin
class Solution {
private val memo = HashMap<Long, Int>()
private lateinit var s: String
private var k: Int = 0
fun maxPartitionsAfterOperations(s: String, k: Int): Int {
this.s = s
this.k = k
memo.clear()
return dp(0, 0, true) + 1
}
private fun dp(index: Int, mask: Int, canChange: Boolean): Int {
if (index == s.length) return 0
val key = (index.toLong() shl 27) or (mask.toLong() shl 1) or if (canChange) 1L else 0L
memo[key]?.let { return it }
val ch = s[index] - 'a'
val maskUpdated = mask or (1 shl ch)
val distinct = Integer.bitCount(maskUpdated)
var res = if (distinct > k) {
1 + dp(index + 1, 1 shl ch, canChange)
} else {
dp(index + 1, maskUpdated, canChange)
}
if (canChange) {
for (nc in 0 until 26) {
val maskNew = mask or (1 shl nc)
val d2 = Integer.bitCount(maskNew)
res = if (d2 > k) {
maxOf(res, 1 + dp(index + 1, 1 shl nc, false))
} else {
maxOf(res, dp(index + 1, maskNew, false))
}
}
}
memo[key] = res
return res
}
}
Python
class Solution:
def maxPartitionsAfterOperations(self, s: str, k: int) -> int:
self.s = s
self.k = k
self.memo: dict[int, int] = {}
return self.dp(0, 0, True) + 1
def dp(self, index: int, mask: int, canChange: bool) -> int:
if index == len(self.s):
return 0
key = (index << 27) | (mask << 1) | (1 if canChange else 0)
if key in self.memo:
return self.memo[key]
ch = ord(self.s[index]) - ord('a')
mask_updated = mask | (1 << ch)
distinct = mask_updated.bit_count()
if distinct > self.k:
res = 1 + self.dp(index + 1, 1 << ch, canChange)
else:
res = self.dp(index + 1, mask_updated, canChange)
if canChange:
for nc in range(26):
mask_new = mask | (1 << nc)
d2 = mask_new.bit_count()
if d2 > self.k:
res = max(res, 1 + self.dp(index + 1, 1 << nc, False))
else:
res = max(res, self.dp(index + 1, mask_new, False))
self.memo[key] = res
return res
Rust
impl Solution {
pub fn maxPartitionsAfterOperations(s: String, k: i32) -> i32 {
let bytes = s.into_bytes();
let n = bytes.len();
let mut memo = std::collections::HashMap::<i64, i32>::new();
fn dp(bytes: &[u8], n: usize, index: usize, mask: i32, can_change: bool, k: i32, memo: &mut std::collections::HashMap<i64, i32>) -> i32 {
if index == n { return 0; }
let key = ((index as i64) << 27) | ((mask as i64) << 1) | if can_change { 1 } else { 0 };
if let Some(&v) = memo.get(&key) { return v; }
let ch = (bytes[index] - b'a') as i32;
let mask_updated = mask | (1 << ch);
let distinct = (mask_updated as u32).count_ones() as i32;
let mut res = if distinct > k {
1 + dp(bytes, n, index + 1, 1 << ch, can_change, k, memo)
} else {
dp(bytes, n, index + 1, mask_updated, can_change, k, memo)
};
if can_change {
for nc in 0..26 {
let mask_new = mask | (1 << nc);
let d2 = (mask_new as u32).count_ones() as i32;
if d2 > k {
res = res.max(1 + dp(bytes, n, index + 1, 1 << nc, false, k, memo));
} else {
res = res.max(dp(bytes, n, index + 1, mask_new, false, k, memo));
}
}
}
memo.insert(key, res);
res
}
dp(&bytes, n, 0, 0, true, k, &mut memo)
}
}
TypeScript
function maxPartitionsAfterOperations(s: string, k: number): number {
const memo = new Map<number, number>();
const n = s.length;
function dp(index: number, mask: number, canChange: boolean): number {
if (index === n) return 0;
const key = (index << 27) | (mask << 1) | (canChange ? 1 : 0);
if (memo.has(key)) return memo.get(key)!;
const ch = s.charCodeAt(index) - 97;
const maskUpdated = mask | (1 << ch);
const distinct = bitCount(maskUpdated);
let res: number;
if (distinct > k) {
res = 1 + dp(index + 1, 1 << ch, canChange);
} else {
res = dp(index + 1, maskUpdated, canChange);
}
if (canChange) {
for (let nc = 0; nc < 26; nc++) {
const maskNew = mask | (1 << nc);
const d2 = bitCount(maskNew);
if (d2 > k) {
res = Math.max(res, 1 + dp(index + 1, 1 << nc, false));
} else {
res = Math.max(res, dp(index + 1, maskNew, false));
}
}
}
memo.set(key, res);
return res;
}
function bitCount(x: number): number {
let c = 0;
while (x) {
x &= x - 1;
c++;
}
return c;
}
return dp(0, 0, true) + 1;
}
Method 3 — Bottom-up DP (iterative)
Intuition
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.
Approach
- Use a sparse map
curmapping a packed key(mask, flag)->completed_partitionsafter processingicharacters. Pack a key as(mask << 1) | flagwhereflagis1if 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
ifrom0ton-1with characterc = s[i]:- Create an empty
nextmap. - For every
(mask, flag)incurwith valueval:- Let
maskUpdated = mask | (1 << (c - 'a')). - If
popcount(maskUpdated) <= k, we can extend current prefix: updatenext[(maskUpdated, flag)] = max(next.get(...), val). Otherwise we must end the current prefix and start a new one containingc: updatenext[(1 << (c - 'a'), flag)] = max(..., val + 1). - If
flagis1, try substitutingcwith everync in [0..25]:- Compute
maskNew = mask | (1 << nc). - If
popcount(maskNew) <= kupdatenext[(maskNew, 0)] = max(..., val)elsenext[(1 << nc, 0)] = max(..., val + 1).
- Compute
- Let
- Replace
curwithnextand continue.
- Create an empty
- After processing all
ncharacters, the answer ismax(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.
Complexity
- ⏰ Time complexity:
O(n * S * 26)– For each of thencharacters we iterate over the current sparse state setSand (whenflagis true) may try up to 26 substitutions; in the worst caseSequals the number of reachable masks times two flags. - 🧺 Space complexity:
O(S)– The sparse maps hold at mostSreachable(mask, flag)states at any step.
Code
Below are implementations using the forward (iterative) DP described above. The Java and Python versions are provided in full; other languages are noted or omitted for brevity.
C++
#include <string>
#include <unordered_map>
#include <climits>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxPartitionsAfterOperations(string s, int k) {
int n = s.size();
unordered_map<int,int> cur;
cur[(0 << 1) | 1] = 0; // pack = (mask << 1) | flag
for (int i = 0; i < n; ++i) {
unordered_map<int,int> next;
int ch = s[i] - 'a';
for (auto &p : cur) {
int packed = p.first;
int val = p.second;
int flag = packed & 1;
int mask = packed >> 1;
int maskUpdated = mask | (1 << ch);
if (__builtin_popcount((unsigned)maskUpdated) <= k) {
int np = (maskUpdated << 1) | flag;
int curv = next.count(np) ? next[np] : INT_MIN;
next[np] = max(curv, val);
} else {
int np = ((1 << ch) << 1) | flag;
int curv = next.count(np) ? next[np] : INT_MIN;
next[np] = max(curv, val + 1);
}
if (flag == 1) {
for (int nc = 0; nc < 26; ++nc) {
int maskNew = mask | (1 << nc);
if (__builtin_popcount((unsigned)maskNew) <= k) {
int np = (maskNew << 1) | 0;
int curv = next.count(np) ? next[np] : INT_MIN;
next[np] = max(curv, val);
} else {
int np = ((1 << nc) << 1) | 0;
int curv = next.count(np) ? next[np] : INT_MIN;
next[np] = max(curv, val + 1);
}
}
}
}
cur.swap(next);
}
int ans = INT_MIN;
for (auto &p : cur) ans = max(ans, p.second);
return ans + 1;
}
};
Go
package main
type Solution struct{}
func (sol *Solution) maxPartitionsAfterOperations(s string, k int) int {
n := len(s)
cur := make(map[int]int)
cur[(0<<1)|1] = 0
for i := 0; i < n; i++ {
next := make(map[int]int)
ch := int(s[i]-'a')
for packed, val := range cur {
flag := packed & 1
mask := packed >> 1
maskUpdated := mask | (1 << ch)
if bitsOn(maskUpdated) <= k {
np := (maskUpdated << 1) | flag
if curv, ok := next[np]; ok {
if val > curv { next[np] = val }
} else {
next[np] = val
}
} else {
np := ((1 << ch) << 1) | flag
if curv, ok := next[np]; ok {
if val+1 > curv { next[np] = val+1 }
} else {
next[np] = val+1
}
}
if flag == 1 {
for nc := 0; nc < 26; nc++ {
maskNew := mask | (1 << nc)
if bitsOn(maskNew) <= k {
np := (maskNew << 1) | 0
if curv, ok := next[np]; ok {
if val > curv { next[np] = val }
} else {
next[np] = val
}
} else {
np := ((1 << nc) << 1) | 0
if curv, ok := next[np]; ok {
if val+1 > curv { next[np] = val+1 }
} else {
next[np] = val+1
}
}
}
}
}
cur = next
}
ans := -1<<31
for _, v := range cur { if v > ans { ans = v } }
return ans + 1
}
func bitsOn(x int) int {
cnt := 0
for x != 0 {
x &= x - 1
cnt++
}
return cnt
}
Java
class Solution {
public int maxPartitionsAfterOperations(String s, int k) {
int n = s.length();
// cur: packedKey -> completed partitions
Map<Integer, Integer> cur = new HashMap<>();
// packed: (mask << 1) | flag
cur.put((0 << 1) | 1, 0);
for (int i = 0; i < n; i++) {
Map<Integer, Integer> next = new HashMap<>();
int ch = s.charAt(i) - 'a';
for (Map.Entry<Integer, Integer> e : cur.entrySet()) {
int packed = e.getKey();
int val = e.getValue();
int flag = packed & 1;
int mask = packed >>> 1;
int maskUpdated = mask | (1 << ch);
if (Integer.bitCount(maskUpdated) <= k) {
int np = (maskUpdated << 1) | flag;
next.put(np, Math.max(next.getOrDefault(np, Integer.MIN_VALUE), val));
} else {
int np = ((1 << ch) << 1) | flag;
next.put(np, Math.max(next.getOrDefault(np, Integer.MIN_VALUE), val + 1));
}
if (flag == 1) {
for (int nc = 0; nc < 26; nc++) {
int maskNew = mask | (1 << nc);
if (Integer.bitCount(maskNew) <= k) {
int np = (maskNew << 1) | 0;
next.put(np, Math.max(next.getOrDefault(np, Integer.MIN_VALUE), val));
} else {
int np = ((1 << nc) << 1) | 0;
next.put(np, Math.max(next.getOrDefault(np, Integer.MIN_VALUE), val + 1));
}
}
}
}
cur = next;
}
int ans = 0;
for (int v : cur.values()) ans = Math.max(ans, v);
return ans + 1;
}
}
Kotlin
class Solution {
fun maxPartitionsAfterOperations(s: String, k: Int): Int {
val n = s.length
var cur = HashMap<Int, Int>()
cur[(0 shl 1) or 1] = 0
for (i in 0 until n) {
val next = HashMap<Int, Int>()
val ch = s[i] - 'a'
for ((packed, val) in cur) {
val flag = packed and 1
val mask = packed ushr 1
val 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 in 0 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
}
}
Python
class Solution:
def maxPartitionsAfterOperations(self, s: str, k: int) -> int:
n = len(s)
# cur: packed_key -> completed partitions ; pack = (mask << 1) | flag
cur: dict[int, int] = {(0 << 1) | 1: 0}
for i in range(n):
nxt: dict[int, int] = {}
ch = ord(s[i]) - ord('a')
for packed, val in cur.items():
flag = packed & 1
mask = packed >> 1
mask_updated = mask | (1 << ch)
if mask_updated.bit_count() <= k:
np = (mask_updated << 1) | flag
nxt[np] = max(nxt.get(np, -10**9), val)
else:
np = ((1 << ch) << 1) | flag
nxt[np] = max(nxt.get(np, -10**9), val + 1)
if flag:
for nc in range(26):
mask_new = mask | (1 << nc)
if mask_new.bit_count() <= k:
np = (mask_new << 1) | 0
nxt[np] = max(nxt.get(np, -10**9), val)
else:
np = ((1 << nc) << 1) | 0
nxt[np] = max(nxt.get(np, -10**9), val + 1)
cur = nxt
ans = 0
for v in cur.values():
ans = max(ans, v)
return ans + 1
Rust
use std::collections::HashMap;
impl Solution {
pub fn max_partitions_after_operations(s: String, k: i32) -> i32 {
let bytes = s.into_bytes();
let n = bytes.len();
let mut cur: HashMap<i64, i32> = HashMap::new();
cur.insert(((0i64) << 1) | 1, 0);
for i in 0..n {
let mut next: HashMap<i64, i32> = HashMap::new();
let ch = (bytes[i] - b'a') as i32;
for (&packed, &val) in cur.iter() {
let flag = (packed & 1) as i32;
let mask = (packed >> 1) as i32;
let mask_updated = mask | (1 << ch);
if (mask_updated as u32).count_ones() as i32 <= k {
let np = ((mask_updated as i64) << 1) | (flag as i64);
let curv = *next.get(&np).unwrap_or(&i32::MIN);
next.insert(np, std::cmp::max(curv, val));
} else {
let np = (((1 << ch) as i64) << 1) | (flag as i64);
let curv = *next.get(&np).unwrap_or(&i32::MIN);
next.insert(np, std::cmp::max(curv, val + 1));
}
if flag == 1 {
for nc in 0..26 {
let mask_new = mask | (1 << nc);
if (mask_new as u32).count_ones() as i32 <= k {
let np = ((mask_new as i64) << 1) | 0;
let curv = *next.get(&np).unwrap_or(&i32::MIN);
next.insert(np, std::cmp::max(curv, val));
} else {
let np = (((1 << nc) as i64) << 1) | 0;
let curv = *next.get(&np).unwrap_or(&i32::MIN);
next.insert(np, std::cmp::max(curv, val + 1));
}
}
}
}
cur = next;
}
let mut ans = i32::MIN;
for &v in cur.values() { ans = ans.max(v); }
ans + 1
}
}
TypeScript
function maxPartitionsAfterOperations(s: string, k: number): number {
const n = s.length;
const cur = new Map<number, number>();
cur.set((0 << 1) | 1, 0);
function bitCount(x: number): number {
let c = 0;
while (x) { x &= x - 1; c++; }
return c;
}
for (let i = 0; i < n; i++) {
const next = new Map<number, number>();
const ch = s.charCodeAt(i) - 97;
for (const [packed, val] of cur.entries()) {
const flag = packed & 1;
const mask = packed >>> 1;
const maskUpdated = mask | (1 << ch);
if (bitCount(maskUpdated) <= k) {
const np = (maskUpdated << 1) | flag;
next.set(np, Math.max(next.get(np) ?? Number.MIN_SAFE_INTEGER, val));
} else {
const np = ((1 << ch) << 1) | flag;
next.set(np, Math.max(next.get(np) ?? Number.MIN_SAFE_INTEGER, val + 1));
}
if (flag === 1) {
for (let nc = 0; nc < 26; nc++) {
const maskNew = mask | (1 << nc);
if (bitCount(maskNew) <= k) {
const np = (maskNew << 1) | 0;
next.set(np, Math.max(next.get(np) ?? Number.MIN_SAFE_INTEGER, val));
} else {
const np = ((1 << nc) << 1) | 0;
next.set(np, Math.max(next.get(np) ?? Number.MIN_SAFE_INTEGER, val + 1));
}
}
}
}
// replace cur with next
cur.clear();
for (const [k1, v1] of next.entries()) cur.set(k1, v1);
}
let ans = Number.MIN_SAFE_INTEGER;
for (const v of cur.values()) ans = Math.max(ans, v);
return ans + 1;
}