题目描述:
给你一个由小写字母组成的字符串 s
,和一个整数 k
。
请你按下面的要求分割字符串:
- 首先,你可以将
s
中的部分字符修改为其他的小写英文字母。 - 接着,你需要把
s
分割成k
个非空且不相交的子串,并且每个子串都是回文串。
请返回以这种方式分割字符串所需修改的最少字符数。
代码思路:
- 动态规划数组
dp
的初始化:dp[i][j]
表示将字符串s
的子串s[i:j+1]
变为回文串所需的最少字符修改次数。- 初始化一个
L x L
的二维数组dp
,其中L
是字符串s
的长度。 - 对于任意
i == j
的情况,即子串只包含一个字符时,这个字符自然是回文的,所以dp[i][j] = 0
。
- 填充动态规划数组
dp
:- 使用双重循环遍历字符串
s
的所有子串。 - 计算
v = int(s[i] != s[j])
,表示如果s[i]
和s[j]
不相等,则需要修改的次数为 1;否则为 0。 - 对于长度小于 3 的子串,直接根据两端的字符是否相等来设置
dp[i][j]
的值。 - 对于长度大于等于 3 的子串,使用状态转移方程
dp[i][j] = dp[i + 1][j - 1] + v
来计算最少修改次数。
- 使用双重循环遍历字符串
- 记忆化搜索函数
dfs
:- 定义函数
dfs(idx, sur)
,返回将字符串s
从索引idx
开始的部分分割成sur
个回文子串所需的最少字符修改次数。 - 基础情况:当
sur == 1
时,返回dp[idx][L-1]
,即将当前剩余部分变为一个回文串所需的最少修改次数。 - 使用循环枚举所有可能的分割点,并计算当前分割方案下的最少修改次数。取所有可能方案中的最小值作为结果。
- 定义函数
- 返回结果:
- 调用
dfs(0, k)
,返回从字符串s
的起始位置开始,分割成k
个回文子串所需的最少字符修改次数。
- 调用
代码实现:
class Solution:
def palindromePartition(self, s: str, k: int) -> int:
L = len(s)
# 定义dp数组, 其中dp[i][j]为s[i:j + 1]变为回文串需要修改的的最少字符数
# 当i == j时, dp[i][j] = 0 (单个字符必然是回文的)
dp = [[0] * L for i in range(L)]
# 转移方程: dp[i][j] = dp[i + 1][j - 1] + (s[i] != s[j]) (参考最长回文子串)
for j in range(L):
for i in range(j):
v = int(s[i] != s[j])
if j - i < 3:
dp[i][j] = v
else:
dp[i][j] = dp[i + 1][j - 1] + v
# 这样我们得到了一个辅助数组,可以快速查询s的某个子串变为回文串需要修改的的最少字符数
# 记忆化搜索
# 定义函数dfs(idx, sur): 返回需要分割sur个字符串时, s[idx:]所需修改的最少字符数。
@cache
def dfs(idx, sur):
# base case: 当只需要分割成1个子串时,只能把当前字符串变为回文串
if sur == 1: return dp[idx][L - 1]
ans = inf
# 枚举分割点取最小值即可, 分割点不能太靠后, 要始终保证s[idx:]的字符数量大于等于sur
# 不然根本无法分割成sur个子串
for i in range(idx, L - sur + 1):
ans = min(ans, dp[idx][i] + dfs(i + 1, sur - 1))
return ans
return dfs(0, k)