Given a string s, you need to partition it into one or more balancedsubstrings. For example, if s == "ababcc" then ("abab", "c", "c"), ("ab", "abc", "c"), and ("ababcc") are all valid partitions, but ("a", "bab", "cc"), ("aba", "bc", "c"), and ("ab", "abcc") are not. The unbalanced substrings are bolded.
Return the minimum number of substrings that you can partition s into.
Note: A balanced string is a string where each character in the string occurs the same number of times.
Example 1:
Input:s = "fabccddg"
Output:3
Explanation:
We can partition the string s into 3 substrings in one of the following ways: ("fab, "ccdd", "g"), or ("fabc", "cd", "dg").
Example 2:
Input:s = "abababaccddb"
Output:2
Explanation:
We can partition the string s into 2 substrings like so: ("abab", "abaccddb").
Constraints:
1 <= s.length <= 1000
s consists only of English lowercase letters.
Solutions
Solution 1: Memoized Search + Hash Table
Thinking
\(s\) must be split into as few balanced pieces as possible, each with equal character frequencies. Full partitions are exponential; \(n\le 1000\) allows quadratic DP.
From index \(i\), extending \(j\) can test balance with frequency maps: a piece is legal when only one frequency remains. The optimum depends only on the start index.
Memoize \(dfs(i)\): maintain \(cnt\) and \(freq\), and when \(freq\) has size \(1\) take \(1+dfs(j+1)\). The empty suffix returns \(0\).
We design a function \(\textit{dfs}(i)\), which represents the minimum number of substrings starting from \(s[i]\). The answer is \(\textit{dfs}(0)\).
The calculation process of the function \(\textit{dfs}(i)\) is as follows:
If \(i \geq n\), it means all characters have been processed, so return \(0\).
Otherwise, we maintain a hash table \(\textit{cnt}\) to represent the frequency of each character in the current substring. Additionally, we maintain a hash table \(\textit{freq}\) to represent the frequency of each character's occurrence count.
Then we enumerate \(j\) from \(i\) to \(n-1\), representing the end position of the current substring. For each \(j\), we update \(\textit{cnt}\) and \(\textit{freq}\), then check if the size of \(\textit{freq}\) is \(1\). If it is, we can split from \(j+1\), and the answer is \(1 + \textit{dfs}(j+1)\). We take the minimum answer among all \(j\) as the return value of the function.
To avoid repeated calculations, we use memoized search.
The time complexity is \(O(n^2)\), and the space complexity is \(O(n \times |\Sigma|)\). Here, \(n\) is the length of the string \(s\), and \(|\Sigma|\) represents the size of the character set, which is \(26\) in this problem.
Method 1 keeps a second map of frequency-of-frequencies, which adds constants and fiddly updates.
Balance is equivalent to length equaling (maximum frequency) times (number of distinct letters). Only \(cnt\) and that maximum \(m\) are required.
While extending \(j\), update \(m\) and recurse when \(j-i+1=m\cdot|cnt|\). The state is still the start index, with a shorter inner loop.
We can optimize Solution 1 by not maintaining the \(\textit{freq}\) hash table. Instead, we only need to maintain a hash table \(\textit{cnt}\), which represents the frequency of each character in the current substring. Additionally, we maintain two variables \(k\) and \(m\) to represent the number of distinct characters in the current substring and the maximum frequency of any character, respectively. For a substring \(s[i..j]\), if \(j-i+1 = m \times k\), then this substring is a balanced substring.
The time complexity is \(O(n^2)\), and the space complexity is \(O(n \times |\Sigma|)\). Here, \(n\) is the length of the string \(s\), and \(|\Sigma|\) represents the size of the character set, which is \(26\) in this problem.
Memoization still pays recursion and cache overhead. The same transition has no aftereffect.
Let \(f[i]\) be the fewest pieces for the prefix of length \(i\). For each right end \(i\), expand left to \(j\) and relax \(f[i+1]\) with \(f[j]+1\) when the piece is balanced.
The table yields \(f[n]\), using linear DP memory plus one count map.
We can convert the memoized search into dynamic programming. Define the state \(f[i]\) as the minimum number of substrings required to partition the first \(i\) characters. Initially, \(f[0] = 0\), and the rest \(f[i] = +\infty\) or \(f[i] = n\).
Next, we enumerate \(i\) from \(0\) to \(n-1\). For each \(i\), we maintain a hash table \(\textit{cnt}\) to represent the frequency of each character in the current substring. Additionally, we maintain two variables \(k\) and \(m\) to represent the number of distinct characters in the current substring and the maximum frequency of any character, respectively. For a substring \(s[j..i]\), if \(i-j+1 = m \times k\), then this substring is a balanced substring. At this point, we can partition from \(j\), so \(f[i+1] = \min(f[i+1], f[j] + 1)\).
The final answer is \(f[n]\).
The time complexity is \(O(n^2)\), and the space complexity is \(O(n + |\Sigma|)\). Here, \(n\) is the length of the string \(s\), and \(|\Sigma|\) represents the size of the character set, which is \(26\) in this problem.