《剑指 Offer》专项突破版 - 面试题 94 : 最少回文分割(C++ 实现)

题目链接最少回文分割

题目

输入一个字符串,请问至少需要分割几次才可以使分割出的每个子字符串都是回文?例如,输入字符串 "aaba",至少需要分割 1 次,从两个相邻字符 'a' 中间切一刀将字符串分割成两个回文子字符串 "a" 和 "aba"。

分析

可以将一个字符串切若干刀使每个子字符串都是回文,也就是说,完成一个分割需要多个步骤,而且每个步骤的分割也面临多个选择。例如,在考虑分割字符串 "aaba" 以最后一个字符 'a' 为结尾字符的回文子字符串时,就有两个选择:一个选择是分割出来的回文子字符串只包含一个字符,即 "a"(此时整个字符串 "aaba" 可以分割出 3 个回文子字符串 "aa"、"b" 和 "a");另一个选择是分割出来的子字符串包含 3 个字符,即 "aba"(此时整个字符串 "aaba" 可以分割出两个回文子字符串,即 "a" 和 "aba")。完成一件事需要多个步骤,而且每步可能面临多个选择,这个问题看起来需要用回溯法解决。但由于这个问题没有要求列出所有符合要求的分割方法,而是只需要计算出最少的分割次数,因此这个问题更适合用动态规划来解决。

分析确定状态转移方程

应用动态规划解决问题的关键在于找出状态转移方程。假设字符串为 S,下标为 i 的字符为 S[i],下标从 j 到 i 的子字符串为 S[j···i]。用 f(i) 表示下标从 0 到 i 的子字符串 S[0···i] 的符合条件的最少分割次数。如果字符串的长度是 n,那么 f(n - 1) 就是这个问题的解

如果子字符串 S[0···i] 本身就是一个回文,那么不需要分割符合要求,此时 f(i) 等于 0。如果子字符串 S[0···i] 不是一个回文,那么对每个下标 j(1 <= j <= i)逐一判断子字符串 S[j···i] 是不是回文。如果是回文,那么这就是一个有效的分割方法,此时的分割次数相当于子字符串 S[0···j-1] 的分割次数再加 1,因为这是将子字符串 S[0···j-1] 按照要求分割之后再在 S[j - 1] 和 S[j] 这两个字符中间再分割一次。因此,f(i) 就是所有符合条件的 j 对应的 f(j - 1) 的最小值加 1

根据状态转移方程写代码

下面以输入字符串 "aaba" 分析 f(i) 的计算过程。当 i 等于 0 时,子字符串 "a" 是回文,因此 f(0) 等于 0。当 i 等于 1 时,子字符串 "aa" 也是回文,因此 f(1) 也等于 0。当 i 等于 2 时,子字符串 "aab" 不再是回文,此时只有一种符合条件的分割方法,将 "aab" 分割成 "aa" 和 "b"。因此,f(2) = f(1) + 1 = 1。当 i 等于 3 时,"aaba" 也不是回文,此时有两种分割方法:一是分割成 "a" 和 "aba",此时 j 等于 1;二是分割成 "aab" 和 "a"("aab" 不是回文,它的最少分割次数 f(2) 之前已经计算过),此时 j 等于 3。所以,f(3) 等于 f(0) 和 f(2) 的最小值加 1,即 f(3) 等于 1。因此,至少分割 1 次就能将字符串 "aaba" 分割成两个回文子字符串(即 "a" 和 "aba")。

代码实现

class Solution {
public:
    int minCut(string s) {
        int n = s.size();
        vector<vector<bool>> isPalindrome(n, vector<bool>(n, false));
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j <= i; ++j)
            {
                if (s[i] == s[j] && (i <= j + 2 || isPalindrome[i - 1][j + 1]))
                    isPalindrome[i][j] = true;
            }
        }
​
        vector<int> dp(n);
        for (int i = 0; i < n; ++i)
        {
            if (isPalindrome[i][0])
            {
                dp[i] = 0;
            }
            else
            {
                dp[i] = i;
                for (int j = 1; j <= i; ++j)
                {
                    if (isPalindrome[i][j])
                    {
                        dp[i] = min(dp[i], dp[j - 1] + 1);
                    }
                }
            }
        }
        return dp[n - 1];
    }
};

为了优化时间复杂度,上述代码进行了预处理,即先判断所有子字符串 S[j···i] 是不是回文,并将子字符串是否为回文的结果保存在 isPalindrome[i][j] 中(即用空间换时间)

上述代码的空间和时间的复杂度都是 O(n^2)

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值