Medium
题目描述
给你一个字符串 s,你需要将它分割成一个或多个平衡子串。
平衡字符串是指字符串中每个字符出现的次数都相同的字符串。
返回将字符串 s 分割成平衡子串的最少数量。
示例 1:
输入:s = "fabccddg"
输出:3
解释:我们可以将字符串 s 分割成 3 个子串,方式之一是:("fab", "ccdd", "g") 或者 ("fabc", "cd", "dg")。
示例 2:
输入:s = "abababaccddb"
输出:2
解释:我们可以将字符串 s 分割成 2 个子串:("abab", "abaccddb")。
提示:
1 <= s.length <= 1000s仅由英文小写字母组成
思路提示:
- 设
dp[i]为以索引i+1结尾的前缀的最小分割数 dp[i]可以通过所有满足j < i且子串s[j+1...i]是平衡的j来计算min(dp[j])
解题思路
这是一道经典的动态规划题目。核心思想是枚举所有可能的分割位置,对于每个位置判断当前子串是否为平衡子串。
解题思路:
- 动态规划状态定义:设
dp[i]表示前i个字符的最小分割数 - 状态转移方程:对于位置
i,枚举所有可能的起始位置j,如果子串s[j:i]是平衡的,则dp[i] = min(dp[i], dp[j] + 1) - 平衡子串判断:统计子串中每个字符的频次,如果所有字符的频次都相同,则为平衡子串
算法步骤:
- 初始化
dp数组,dp[0] = 0(空字符串分割数为0) - 对于每个位置
i,枚举所有可能的起始位置j < i - 检查子串
s[j:i]是否为平衡子串:统计字符频次,判断是否所有字符频次相等 - 如果是平衡子串,更新
dp[i] = min(dp[i], dp[j] + 1) - 返回
dp[n],其中n是字符串长度
时间复杂度为 O(n³),其中 n 是字符串长度。虽然看起来复杂度较高,但由于字符集大小有限(26个字母),实际运行效果良好。
代码实现
class Solution {
public:
int minimumSubstringsInPartition(string s) {
int n = s.length();
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (isBalanced(s, j, i - 1)) {
dp[i] = min(dp[i], dp[j] + 1);
}
}
}
return dp[n];
}
private:
bool isBalanced(const string& s, int start, int end) {
unordered_map<char, int> freq;
for (int i = start; i <= end; i++) {
freq[s[i]]++;
}
int expectedFreq = freq.begin()->second;
for (auto& p : freq) {
if (p.second != expectedFreq) {
return false;
}
}
return true;
}
};
class Solution:
def minimumSubstringsInPartition(self, s: str) -> int:
n = len(s)
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
for j in range(i):
if self.is_balanced(s, j, i - 1):
dp[i] = min(dp[i], dp[j] + 1)
return dp[n]
def is_balanced(self, s: str, start: int, end: int) -> bool:
freq = {}
for i in range(start, end + 1):
freq[s[i]] = freq.get(s[i], 0) + 1
frequencies = list(freq.values())
return len(set(frequencies)) == 1
public class Solution {
public int MinimumSubstringsInPartition(string s) {
int n = s.Length;
int[] dp = new int[n + 1];
Array.Fill(dp, int.MaxValue);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (IsBalanced(s, j, i - 1)) {
dp[i] = Math.Min(dp[i], dp[j] + 1);
}
}
}
return dp[n];
}
private bool IsBalanced(string s, int start, int end) {
Dictionary<char, int> freq = new Dictionary<char, int>();
for (int i = start; i <= end; i++) {
if (freq.ContainsKey(s[i])) {
freq[s[i]]++;
} else {
freq[s[i]] = 1;
}
}
int expectedFreq = freq.Values.First();
return freq.Values.All(f => f == expectedFreq);
}
}
var minimumSubstringsInPartition = function(s) {
const n = s.length;
const dp = new Array(n + 1).fill(Infinity);
dp[0] = 0;
function isBalanced(start, end) {
const freq = {};
for (let i = start; i <= end; i++) {
freq[s[i]] = (freq[s[i]] || 0) + 1;
}
const counts = Object.values(freq);
return counts.every(count => count === counts[0]);
}
for (let i = 1; i <= n; i++) {
for (let j = 0; j < i; j++) {
if (isBalanced(j, i - 1)) {
dp[i] = Math.min(dp[i], dp[j] + 1);
}
}
}
return dp[n];
};
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(n³) |
| 空间复杂度 | O(n) |
说明:
- 时间复杂度:外层循环 O(n),内层循环 O(n),判断平衡子串 O(n),总计 O(n³)
- 空间复杂度:dp 数组占用 O(n) 空间,哈希表最多存储 26 个字符,为常数空间