题目
You are given a string s containing lowercase letters and an integer k. You need to :
First, change some characters of s to other lowercase English letters.
Then divide s into k non-empty disjoint substrings such that each substring is palindrome.
Return the minimal number of characters that you need to change to divide the string.
Example 1:
Input: s = “abc”, k = 2
Output: 1
Explanation: You can split the string into “ab” and “c”, and change 1 character in “ab” to make it palindrome.
Example 2:
Input: s = “aabbc”, k = 3
Output: 0
Explanation: You can split the string into “aa”, “bb” and “c”, all of them are palindrome.
Example 3:
Input: s = “leetcode”, k = 8
Output: 0
Constraints:
1 <= k <= s.length <= 100.
s only contains lowercase English letters.
给出字符s和切割成回文的个数k,求得需要修改字符串的最小数量
- 字符串都是小写字符
- 切割的回文是非空字符串
解题思路
动态规划:
- dp[i][j] 表示将字符串 s 前 j+1 位的子串切割成 i+1 个回文时,需要修改字符的最小数。
- 设 s 为 “aabbc”,k 为 3 ,初始化dp[k][len]=[[Int.max, …] …],len 为 s 的长度。
- dp[0] = [[0,0,1,2,2], …]。
- 求 dp[1] 时,由于切割成两个回文,所以从前两位子串 “aa” ,开始切割。
- 切割为 “a” “a”,“a” 切割成 1 个回文所需修改最小字符数为 dp[0][0] ,“a” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等min (dp[1][1],dp[0][0]+0)=0。
- 更换子串为"aab",切割为 “a” “ab”,“a” 切割成 1 个回文所需修改最小字符数为 dp[0][0] ,“ab” 切割成 1 个回文所需修改字符数求得 1 ,dp[1][1] 暂等 min (dp[1][1],dp[0][0]+1)=1。
- 切割为 “aa” “b”,“aa” 切割成 1 个回文所需修改最小字符数为 dp[0][1] ,“b” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等 min (dp[1][1],dp[0][1]+0)=0。
- 更换子串为"aabb",切割为 “a” “abb”,“a” 切割成 1 个回文所需修改最小字符数为 dp[0][0] ,“abb” 切割成 1 个回文所需修改字符数求得 1 ,dp[1][1] 暂等 min (dp[1][1],dp[0][0]+1)=0。
- 切割为 “aa” “bb”,“aa” 切割成 1 个回文所需修改最小字符数为 dp[0][1] ,“bb” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等 min (dp[1][1],dp[0][1]+0)=0。
10.切割为 “aab” “b”,“aab” 切割成 1 个回文所需修改最小字符数为 dp[0][2] ,“b” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等 min (dp[1][1],dp[0][2]+0)=0。 - …
- 切割为 “aabb” “c”,“aabb” 切割成 1 个回文所需修改最小字符数为 dp[0][3] ,“c” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等 min (dp[1][1],dp[0][3]+0)=1。
- dp[1] = […, [Int.max, 0, 0, 0, 1], …]
- …
- dp[i][j] = dp[i-1][k] + oneP(s[loc: k, len: len-k]),oneP 可求得切得 1 个回文所需修改字符的最小数,最终 dp[i][j] 取值为 k = i-1 ~ len-1,中得值得最小值
代码实现
func palindromePartition(_ s: String, _ k: Int) -> Int {
// 字符串的长度
let len = s.count
// 创建动态规划数组dp[i][j],表示前j+1位子串分割成i+1个回文所需要改变字符的最小个数
var dp = Array(repeating: Array(repeating: Int.max, count: len), count: k)
// 初始化切割成0+1=1个回文时所需改变字符最小数
for i in 0 ... len-1 {
dp[0][i] = self.initOnePartition(str: s.prefix(i+1))
}
// 只有k>1时,才需要之后的操作
if k>1 {
// 循环从切割1+1=2到k个回文
for i in 1...k-1 {
// 循环从前i位子串到len位子串
for j in i ... len-1 {
// 当j==i时,即将子串的每个字符切割成回文,不用改变字符
if j==i {
dp[i][j] = 0
} else {
// 当子串的长度开始大于切割的回文数时,循环切割前i至前j-1位子串
for t in i-1 ... j-1 {
// 获取t+1至j的子串,并计算回文修改字符最小数tail
let tail = self.initOnePartition(str: s.prefix(j+1).suffix(j-t))
// print("tail is ",tail)
// 将前t位子串切割成i-1个回文所需修改最小数,与tail相加,即为本次所需修改字符数
let temp = dp[i-1][t] + tail
// 将此次循环的最小数存于dp[i][j]中
dp[i][j] = min(dp[i][j], temp)
}
}
}
}
}
// 返回将s切成k个回文所需修改字符最小数
return dp[k-1][len-1];
}
// 切割成一个回文需要修改字符的最小数
func initOnePartition(str:Substring) -> Int {
let endIndex = str.endIndex
// 循环次数不会字符长度的超过一半
var num = 0
let len = str.count/2
// 初始化结果
var result = 0
// 循环字符串
for ch in str {
// 当循环到字符长度一半次数时,退出循环
if (num >= len) {
break
}
// 获取前后对应字符并比较
let indexE = str.index(endIndex, offsetBy: -1*(num+1))
let chE = str[indexE]
// 若不相等,则需要修改字符,结果+1
if(ch != chE){
result += 1
}
num += 1
}
return result
}