Hard
题目描述
给你一个字符串 s 和一个正整数 k。
从字符串 s 中选择一组非重叠的子串,满足以下条件:
- 每个子串的长度至少为
k。 - 每个子串都是回文串。
返回在最优选择中子串的最多数目。
子串是字符串中一个连续的字符序列。
示例 1:
输入: s = "abaccdbbd", k = 3
输出: 2
解释: 我们可以选择 s = "abaccdbbd" 中带下划线的子串。"aba" 和 "dbbd" 都是回文串且长度至少为 k = 3。
可以证明我们无法找到包含两个以上有效子串的选择。
示例 2:
输入: s = "adbcda", k = 2
输出: 0
解释: 字符串中没有长度至少为 2 的回文子串。
提示:
1 <= k <= s.length <= 2000s仅由小写英文字母组成
解题思路
解题思路
这是一个经典的动态规划问题,结合贪心策略来求解。
核心思想: 使用动态规划来记录以每个位置结尾时能得到的最大回文子串数量。对于每个位置,我们考虑所有可能的回文子串结尾情况,选择能带来最大收益的方案。
算法步骤:
- 预处理回文判断: 使用中心扩展法或动态规划预处理所有子串的回文性质
- 状态定义:
dp[i]表示考虑字符串前i个字符时能得到的最大回文子串数量 - 状态转移: 对于位置
i,我们有两种选择:- 不选择以位置
i结尾的任何回文子串:dp[i] = dp[i-1] - 选择一个以位置
i结尾且长度至少为k的回文子串:dp[i] = max(dp[i], dp[j] + 1),其中j是该回文子串的起始位置前一位
- 不选择以位置
- 贪心优化: 当找到合法回文子串时,优先选择最早结束的(最短的),这样为后续选择留出更多空间
时间复杂度分析: 预处理回文需要 O(n²),DP 转移也是 O(n²),总体 O(n²) 空间复杂度: 需要 O(n²) 存储回文信息和 O(n) 的 DP 数组
代码实现
class Solution {
public:
int maxPalindromes(string s, int k) {
int n = s.length();
vector<vector<bool>> isPalindrome(n, vector<bool>(n, false));
// 预处理所有子串的回文性质
for (int i = 0; i < n; i++) {
isPalindrome[i][i] = true;
}
for (int i = 0; i < n - 1; i++) {
isPalindrome[i][i + 1] = (s[i] == s[i + 1]);
}
for (int len = 3; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
isPalindrome[i][j] = (s[i] == s[j]) && isPalindrome[i + 1][j - 1];
}
}
vector<int> dp(n + 1, 0);
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1]; // 不选择以 i-1 结尾的回文串
for (int j = 0; j < i; j++) {
if (i - j >= k && isPalindrome[j][i - 1]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
return dp[n];
}
};
class Solution:
def maxPalindromes(self, s: str, k: int) -> int:
n = len(s)
# 预处理所有子串的回文性质
is_palindrome = [[False] * n for _ in range(n)]
# 单个字符都是回文
for i in range(n):
is_palindrome[i][i] = True
# 长度为2的子串
for i in range(n - 1):
is_palindrome[i][i + 1] = (s[i] == s[i + 1])
# 长度大于2的子串
for length in range(3, n + 1):
for i in range(n - length + 1):
j = i + length - 1
is_palindrome[i][j] = (s[i] == s[j]) and is_palindrome[i + 1][j - 1]
# 动态规划
dp = [0] * (n + 1)
for i in range(1, n + 1):
dp[i] = dp[i - 1] # 不选择以 i-1 结尾的回文串
for j in range(i):
if i - j >= k and is_palindrome[j][i - 1]:
dp[i] = max(dp[i], dp[j] + 1)
return dp[n]
public class Solution {
public int MaxPalindromes(string s, int k) {
int n = s.Length;
bool[,] isPalindrome = new bool[n, n];
// 预处理所有子串的回文性质
for (int i = 0; i < n; i++) {
isPalindrome[i, i] = true;
}
for (int i = 0; i < n - 1; i++) {
isPalindrome[i, i + 1] = (s[i] == s[i + 1]);
}
for (int len = 3; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
isPalindrome[i, j] = (s[i] == s[j]) && isPalindrome[i + 1, j - 1];
}
}
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1]; // 不选择以 i-1 结尾的回文串
for (int j = 0; j < i; j++) {
if (i - j >= k && isPalindrome[j, i - 1]) {
dp[i] = Math.Max(dp[i], dp[j] + 1);
}
}
}
return dp[n];
}
}
var maxPalindromes = function(s, k) {
const n = s.length;
const isPalindrome = Array(n).fill().map(() => Array(n).fill(false));
// 预处理所有子串的回文性质
for (let i = 0; i < n; i++) {
isPalindrome[i][i] = true;
}
for (let i = 0; i < n - 1; i++) {
isPalindrome[i][i + 1] = (s[i]
复杂度分析
| 复杂度类型 | 大小 |
|---|---|
| 时间复杂度 | O(n²) |
| 空间复杂度 | O(n²) |
- 时间复杂度:O(n²),其中 n 是字符串长度。预处理回文子串需要 O(n²),动态规划转移也需要 O(n²)
- 空间复杂度:O(n²),主要用于存储
isPalindrome数组,DP 数组只需要 O(n) 空间