【算法】Palindrome Partitioning III 回文分区3

本文介绍了一种使用动态规划算法解决字符串切割成多个回文子串的问题,旨在找到最少字符修改数。通过实例解析,详细阐述了算法流程,包括状态定义、转移方程及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

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,求得需要修改字符串的最小数量

  1. 字符串都是小写字符
  2. 切割的回文是非空字符串

解题思路

动态规划:

  1. dp[i][j] 表示将字符串 s 前 j+1 位的子串切割成 i+1 个回文时,需要修改字符的最小数。
  2. 设 s 为 “aabbc”,k 为 3 ,初始化dp[k][len]=[[Int.max, …] …],len 为 s 的长度。
  3. dp[0] = [[0,0,1,2,2], …]。
  4. 求 dp[1] 时,由于切割成两个回文,所以从前两位子串 “aa” ,开始切割。
  5. 切割为 “a” “a”,“a” 切割成 1 个回文所需修改最小字符数为 dp[0][0] ,“a” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等min (dp[1][1],dp[0][0]+0)=0。
  6. 更换子串为"aab",切割为 “a” “ab”,“a” 切割成 1 个回文所需修改最小字符数为 dp[0][0] ,“ab” 切割成 1 个回文所需修改字符数求得 1 ,dp[1][1] 暂等 min (dp[1][1],dp[0][0]+1)=1。
  7. 切割为 “aa” “b”,“aa” 切割成 1 个回文所需修改最小字符数为 dp[0][1] ,“b” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等 min (dp[1][1],dp[0][1]+0)=0。
  8. 更换子串为"aabb",切割为 “a” “abb”,“a” 切割成 1 个回文所需修改最小字符数为 dp[0][0] ,“abb” 切割成 1 个回文所需修改字符数求得 1 ,dp[1][1] 暂等 min (dp[1][1],dp[0][0]+1)=0。
  9. 切割为 “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。
  10. 切割为 “aabb” “c”,“aabb” 切割成 1 个回文所需修改最小字符数为 dp[0][3] ,“c” 切割成 1 个回文所需修改字符数求得 0 ,dp[1][1] 暂等 min (dp[1][1],dp[0][3]+0)=1。
  11. dp[1] = […, [Int.max, 0, 0, 0, 1], …]
  12. 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
    }

代码地址:https://github.com/sinianshou/EGSwiftLearning

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值