Hard
题目描述
给你一个由数字 ‘1’ 到 ‘9’ 组成的字符串 s 和两个整数 k 和 minLength。
如果 s 的一个分割满足以下条件,则称其为 美丽分割:
- s 被分割成 k 个不重叠的子字符串。
- 每个子字符串的长度至少为 minLength。
- 每个子字符串以质数数字开头,以非质数数字结尾。质数数字为 ‘2’、‘3’、‘5’ 和 ‘7’,其余数字为非质数。
返回 s 的美丽分割数目。由于答案可能很大,请返回对 10^9 + 7 取模的结果。
子字符串是字符串中连续的字符序列。
示例 1:
输入:s = "23542185131", k = 3, minLength = 2
输出:3
解释:存在三种创建美丽分割的方式:
"2354 | 218 | 5131"
"2354 | 21851 | 31"
"2354218 | 51 | 31"
示例 2:
输入:s = "23542185131", k = 3, minLength = 3
输出:1
解释:存在一种创建美丽分割的方式:"2354 | 218 | 5131"。
示例 3:
输入:s = "3312958", k = 3, minLength = 1
输出:1
解释:存在一种创建美丽分割的方式:"331 | 29 | 58"。
约束条件:
- 1 <= k, minLength <= s.length <= 1000
- s 由数字 ‘1’ 到 ‘9’ 组成。
解题思路
这道题需要将字符串分割成 k 个子串,每个子串都必须满足美丽分割的条件。
核心思路分析:
美丽子串条件识别:首先需要判断一个子串是否美丽,即以质数开头(2,3,5,7)且以非质数结尾(1,4,6,8,9)。
动态规划状态设计:定义 dp[i][j] 表示前 i 个字符分割成 j 个美丽子串的方案数。
状态转移:对于每个位置 i 和分割数 j,枚举上一个分割点 prev,检查从 prev 到 i-1 的子串是否为美丽子串,如果是则累加 dp[prev][j-1] 的方案数。
优化思路:
由于需要频繁检查子串是否美丽,可以预处理所有可能的美丽子串起始和结束位置,避免重复计算。另外,由于约束条件,第一个子串必须从位置0开始且以质数开头,最后一个子串必须以非质数结尾。
边界条件处理:
- 如果字符串首字符不是质数或末字符不是非质数,直接返回0
- 如果字符串长度小于 k * minLength,也直接返回0
时间复杂度为 O(n^3 * k),空间复杂度为 O(n * k),其中 n 为字符串长度。
代码实现
class Solution {
public:
int beautifulPartitions(string s, int k, int minLength) {
int n = s.length();
const int MOD = 1e9 + 7;
auto isPrime = [](char c) {
return c == '2' || c == '3' || c == '5' || c == '7';
};
// 检查字符串是否可能形成美丽分割
if (!isPrime(s[0]) || isPrime(s[n-1]) || n < k * minLength) {
return 0;
}
// 预处理:标记每个位置是否可以作为美丽子串的结束位置
vector<bool> canEnd(n, false);
for (int i = minLength - 1; i < n; i++) {
if (!isPrime(s[i])) {
canEnd[i] = true;
}
}
// dp[i][j] 表示前i个字符分割成j个美丽子串的方案数
vector<vector<long long>> dp(n + 1, vector<long long>(k + 1, 0));
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= min(i / minLength, k); j++) {
dp[i][j] = dp[i-1][j]; // 不在位置i-1结束分割
if (j > 0 && canEnd[i-1]) {
// 在位置i-1结束一个分割
for (int prev = (j-1) * minLength; prev <= i - minLength; prev++) {
if (prev == 0 || (canEnd[prev-1] && isPrime(s[prev]))) {
dp[i][j] = (dp[i][j] + dp[prev][j-1]) % MOD;
}
}
}
}
}
return dp[n][k];
}
};
class Solution:
def beautifulPartitions(self, s: str, k: int, minLength: int) -> int:
n = len(s)
MOD = 10**9 + 7
def is_prime(c):
return c in '2357'
# 检查字符串是否可能形成美丽分割
if not is_prime(s[0]) or is_prime(s[-1]) or n < k * minLength:
return 0
# 预处理:标记每个位置是否可以作为美丽子串的结束位置
can_end = [False] * n
for i in range(minLength - 1, n):
if not is_prime(s[i]):
can_end[i] = True
# dp[i][j] 表示前i个字符分割成j个美丽子串的方案数
dp = [[0] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
for j in range(min(i // minLength, k) + 1):
dp[i][j] = dp[i-1][j] # 不在位置i-1结束分割
if j > 0 and can_end[i-1]:
# 在位置i-1结束一个分割
for prev in range((j-1) * minLength, i - minLength + 1):
if prev == 0 or (can_end[prev-1] and is_prime(s[prev])):
dp[i][j] = (dp[i][j] + dp[prev][j-1]) % MOD
return dp[n][k]
public class Solution {
public int BeautifulPartitions(string s, int k, int minLength) {
int n = s.Length;
const int MOD = 1000000007;
bool IsPrime(char c) {
return c == '2' || c == '3' || c == '5' || c == '7';
}
// 检查字符串是否可能形成美丽分割
if (!IsPrime(s[0]) || IsPrime(s[n-1]) || n < k * minLength) {
return 0;
}
// 预处理:标记每个位置是否可以作为美丽子串的结束位置
bool[] canEnd = new bool[n];
for (int i = minLength - 1; i < n; i++) {
if (!IsPrime(s[i])) {
canEnd[i] = true;
}
}
// dp[i][j] 表示前i个字符分割成j个美丽子串的方案数
long[,] dp = new long[n + 1, k + 1];
dp[0, 0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= Math.Min(i / minLength, k); j++) {
dp[i, j] = dp[i-1, j]; // 不在位置i-1结束分割
if (j > 0 && canEnd[i-1]) {
// 在位置i-1结束一个分割
for (int prev = (j-1) * minLength; prev <= i - minLength; prev++) {
if (prev == 0 || (canEnd[prev-1] && IsPrime(s[prev]))) {
dp[i, j] = (dp[i, j] + dp[prev, j-1]) % MOD;
}
}
}
}
}
return (int)dp[n, k];
}
}
var beautifulPartitions = function(s, k, minLength) {
const MOD = 1000000007;
const n = s.length;
const isPrime = (c) => '2357'.includes(c);
const isNonPrime = (c) => '14689'.includes(c);
if (k * minLength > n || !isPrime(s[0]) || !isNonPrime(s[n-1])) {
return 0;
}
const dp = Array(k + 1).fill().map(() => Array(n + 1).fill(0));
dp[0][0] = 1;
for (let i = 1; i <= k; i++) {
for (let j = i * minLength; j <= n; j++) {
for (let prev = (i - 1) * minLength; prev <= j - minLength; prev++) {
if (dp[i-1][prev] === 0) continue;
if (prev === 0 || (isNonPrime(s[prev-1]) && isPrime(s[prev]))) {
if (j === n || isNonPrime(s[j-1])) {
dp[i][j] = (dp[i][j] + dp[i-1][prev]) % MOD;
}
}
}
}
}
return dp[k][n];
};
复杂度分析
| 复杂度类型 | 分析结果 |
|---|---|
| 时间复杂度 | O(n² × k) |
| 空间复杂度 | O(n × k) |
其中 n 是字符串长度,k 是分割数目。时间复杂度主要来自三重循环:外层遍历位置,中层遍历分割数,内层遍历前一个分割点。空间复杂度来自二维DP数组的存储。
相关题目
. Restore The Array (Hard)