Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

Example:

Input: "aab"
Output: 1
Explanation: The palindrome partitioning ["aa","b"] could be produced using 1 cut.

这道题是让找到把原字符串拆分成回文串的最小切割数,如果我们首先考虑用brute force来做的话就会十分的复杂,因为我们不但要判断子串是否是回文串,而且还要找出最小切割数,情况会非常的多,不好做。所以对于这种玩字符串且是求极值的题,就要祭出旷古神器动态规划Dynamic Programming了,秒天秒地秒空气,DP在手天下我有。好,吹完一波后,开始做题。DP解法的两个步骤,定义dp数组和找状态转移方程。首先来定义dp数组,这里使用最直接的定义方法,一维的dp数组,其中dp[i]表示子串 [0, i] 范围内的最小分割数,那么我们最终要返回的就是 dp[n-1] 了,这里先加个corner case的判断,若s串为空,直接返回0,OJ的test case中并没有空串的检测,但博主认为还是加上比较好,毕竟空串也算是回文串的一种,所以最小分割数为0也说得过去。接下来就是大难点了,如何找出状态转移方程。

如何更新dp[i]呢,前面说过了其表示子串 [0, i] 范围内的最小分割数。那么这个区间的每个位置都可以尝试分割开来,所以就用一个变量j来从0遍历到i,这样就可以把区间 [0, i] 分为两部分,[0, j-1] 和 [j, i],那么suppose我们已经知道区间 [0, j-1] 的最小分割数 dp[j-1],因为我们是从前往后更新的,而 j 小于等于 i,所以 dp[j-1] 肯定在 dp[i] 之前就已经算出来了。这样我们就只需要判断区间 [j, i] 内的子串是否为回文串了,是的话,dp[i] 就可以用 1 + dp[j-1] 来更新了。判断子串的方法用的是之前那道 Palindromic Substrings 一样的方法,使用一个二维的dp数组p,其中 p[i][j] 表示区间 [i, j] 内的子串是否为回文串,其状态转移方程为 p[i][j] = (s[i] == s[j]) && p[i+1][j-1],其中 p[i][j] = true if [i, j]为回文。这样的话,这道题实际相当于同时用了两个DP的方法,确实难度不小呢。

第一个for循环遍历的是i,此时我们现将 dp[i] 初始化为 i,因为对于区间 [0, i],就算我们每个字母割一刀(怎么听起来像凌迟?!),最多能只用分割 i 次,不需要再多于这个数字。但是可能会变小,所以第二个for循环用 j 遍历区间 [0, j],根据上面的解释,我们需要验证的是区间 [j, i] 内的子串是否为回文串,那么只要 s[j] == s[i],并且 i-j < 2 或者 p[j+1][i-1] 为true的话,先更新 p[j][i] 为true,然后在更新 dp[i],这里需要注意一下corner case,当 j=0 时,我们直接给 dp[i] 赋值为0,因为此时能运行到这,说明 [j, i] 区间是回文串,而 j=0, 则说明 [0, i] 区间内是回文串,这样根本不用分割啊。若 j 大于0,则用 dp[j-1] + 1 来更新 dp[i],最终返回 dp[n-1] 即可,参见代码如下:

解法一:

class Solution {
public:
int minCut(string s) {
if (s.empty()) return ;
int n = s.size();
vector<vector<bool>> p(n, vector<bool>(n));
vector<int> dp(n);
for (int i = ; i < n; ++i) {
dp[i] = i;
for (int j = ; j <= i; ++j) {
if (s[i] == s[j] && (i - j < || p[j + ][i - ])) {
p[j][i] = true;
dp[i] = (j == ) ? : min(dp[i], dp[j - ] + );
}
}
}
return dp[n - ];
}
};

我们也可以反向推,这里的dp数组的定义就刚好跟前面反过来了,dp[i] 表示区间 [i, n-1] 内的最小分割数,所以最终只需要返回 dp[0] 就是区间 [0, n-1] 内的最喜哦啊分割数了,极为所求。然后每次初始化 dp[i] 为 n-1-i 即可,j 的更新范围是 [i, n),此时我们就只需要用 1 + dp[j+1] 来更新 dp[i] 了,为了防止越界,需要对 j == n-1 的情况单独处理一下,整个思想跟上面的解法一模一样,请参见之前的讲解。

解法二:

class Solution {
public:
int minCut(string s) {
if (s.empty()) return ;
int n = s.size();
vector<vector<bool>> p(n, vector<bool>(n));
vector<int> dp(n);
for (int i = n - ; i >= ; --i) {
dp[i] = n - i - ;
for (int j = i; j < n; ++j) {
if (s[i] == s[j] && (j - i <= || p[i + ][j - ])) {
p[i][j] = true;
dp[i] = (j == n - ) ? : min(dp[i], dp[j + ] + );
}
}
}
return dp[];
}
};

下面这种解法是论坛上的高分解法,没用使用判断区间 [i, j] 内是否为回文串的二维dp数组,节省了空间。但写法上比之前的解法稍微有些凌乱,也算是个 trade-off 吧。这里还是用的一维dp数组,不过大小初始化为了 n+1,这样其定义就稍稍发生了些变化,dp[i] 表示由s串中前 i 个字母组成的子串的最小分割数,这样 dp[n] 极为最终所求。接下来就要找状态转移方程了。这道题的更新方式比较特别,跟之前的都不一样,之前遍历 i 的时候,都是更新的 dp[i],这道题更新的却是 dp[i+len+1] 和 dp[i+len+2],其中 len 是以i为中心,总长度为 2*len + 1 的回文串,比如 bob,此时 i=1,len=1,或者是i为中心之一,总长度为 2*len + 2 的回文串,比如 noon,此时 i=1,len=1。中间两个for循环就是分别更新以 i 为中心且长度为 2*len + 1 的奇数回文串,和以 i 为中心之一且长度为 2*len + 2 的偶数回文串的。i-len 正好是奇数或者偶数回文串的起始位置,由于我们定义的 dp[i] 是区间 [0, i-1] 的最小分割数,所以 dp[i-len] 就是区间 [0, i-len-1] 范围内的最小分割数,那么加上奇数回文串长度 2*len + 1,此时整个区间为 [0, i+len],即需要更新 dp[i+len+1]。如果是加上偶数回文串的长度 2*len + 2,那么整个区间为 [0, i+len+1],即需要更新 dp[i+len+2]。这就是分奇偶的状态转移方程,参见代码如下:

解法三:

class Solution {
public:
int minCut(string s) {
if (s.empty()) return ;
int n = s.size();
vector<int> dp(n + , INT_MAX);
dp[] = -;
for (int i = ; i < n; ++i) {
for (int len = ; i - len >= && i + len < n && s[i - len] == s[i + len]; ++len) {
dp[i + len + ] = min(dp[i + len + ], + dp[i - len]);
}
for (int len = ; i - len >= && i + len + < n && s[i - len] == s[i + len + ]; ++len) {
dp[i + len + ] = min(dp[i + len + ], + dp[i - len]);
}
}
return dp[n];
}
};

类似题目:

Palindrome Partitioning

Palindromic Substrings

参考资料:

https://leetcode.com/problems/palindrome-partitioning-ii/

https://leetcode.com/problems/palindrome-partitioning-ii/discuss/42213/Easiest-Java-DP-Solution-(97.36)

https://leetcode.com/problems/palindrome-partitioning-ii/discuss/42199/My-DP-Solution-(-explanation-and-code)

https://leetcode.com/problems/palindrome-partitioning-ii/discuss/42212/Two-C%2B%2B-versions-given-(one-DP-28ms-one-Manancher-like-algorithm-10-ms)

https://leetcode.com/problems/palindrome-partitioning-ii/discuss/42198/My-solution-does-not-need-a-table-for-palindrome-is-it-right-It-uses-only-O(n)-space.

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Palindrome Partitioning II 拆分回文串之二的更多相关文章

  1. 132 Palindrome Partitioning II 分割回文串 II

    给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串.返回 s 符合要求的的最少分割次数.例如,给出 s = "aab",返回 1 因为进行一次分割可以将字符串 s 分 ...

  2. 19. Palindrome Partitioning && Palindrome Partitioning II (回文分割)

    Palindrome Partitioning Given a string s, partition s such that every substring of the partition is ...

  3. [LeetCode] Palindrome Partitioning II 解题笔记

    Given a string s, partition s such that every substring of the partition is a palindrome. Return the ...

  4. [LeetCode] Palindrome Partitioning 拆分回文串

    Given a string s, partition s such that every substring of the partition is a palindrome. Return all ...

  5. [LeetCode] Valid Palindrome II 验证回文字符串之二

    Given a non-empty string s, you may delete at most one character. Judge whether you can make it a pa ...

  6. [LeetCode] Find the Closest Palindrome 寻找最近的回文串

    Given an integer n, find the closest integer (not including itself), which is a palindrome. The 'clo ...

  7. Leetcode: Palindrome Partitioning II

    参考:http://www.cppblog.com/wicbnu/archive/2013/03/18/198565.html 我太喜欢用dfs和回溯法了,但是这些暴力的方法加上剪枝之后复杂度依然是很 ...

  8. 131. 132. Palindrome Partitioning *HARD* -- 分割回文字符串

    131. Palindrome Partitioning Given a string s, partition s such that every substring of the partitio ...

  9. LeetCode: Palindrome Partitioning II 解题报告

    Palindrome Partitioning II Given a string s, partition s such that every substring of the partition ...

随机推荐

  1. C#解决界面不响应

    在我们的程序中,经常会有一些耗时较长的运算,为了保证用户体验,不引起界面不响应,我们一般会采用多线程操作,让耗时操作在后台完成,完成后再进行处理或给出提示,在运行中,也会时时去刷新界面上的进度条等显示 ...

  2. WCF学习系列二---【WCF Interview Questions – Part 2 翻译系列】

    http://www.topwcftutorials.net/2012/09/wcf-faqs-part2.html WCF Interview Questions – Part 2 This WCF ...

  3. JAVA 日期格式工具类DateUtil.java

    DateUtil.java package pers.kangxu.datautils.utils; import java.text.SimpleDateFormat; import java.ut ...

  4. C++_系列自学课程_第_11_课_类型转换_《C++ Primer 第四版》

    上次说了关于表达式的一些内容,说到还有一些关于数据类型转换的内容,今天我们接着八一八C++中的数据类型转换. 一.隐式类型转换 在表达式中,有些操作符可以对多种类型的操作数进行操作, 例如 + 操作符 ...

  5. linux中字体的安装以及Terminal字体重叠问题解决

    安装wps的时候,经常会提示你系统字体缺失,这些字体网上都有,就不分享了,直接讲安装吧. 就比如这个Wingdings字体,在字体目录中新建一个目录Wingdings,将ttf字体文件复制进去,在终端 ...

  6. 【前端构建】WebPack实例与前端性能优化

    计划把微信的文章也搬一份上来. 这篇主要介绍一下我在玩Webpack过程中的心得.通过实例介绍WebPack的安装,插件使用及加载策略.感受构建工具给前端优化工作带来的便利. 壹 | Fisrt 曾几 ...

  7. arcgis api for js入门开发系列二不同地图服务展示(含源代码)

    上一篇介绍了arcgis api离线部署,这篇开始正式介绍arcgis api for js开发:想要学习webgis开发,首先得熟悉了解前端技术,比如界面布局设计的html+css,核心的是java ...

  8. Android中使用Notification实现宽视图通知栏(Notification示例二)

    Notification是在你的应用常规界面之外展示的消息.当app让系统发送一个消息的时候,消息首先以图表的形式显示在通知栏.要查看消息的详情需要进入通知抽屉(notificationdrawer) ...

  9. java中File类的使用

    public class FileLei {    public static void main(String[] args) throws IOException {        //..表示上 ...

  10. 尝试解析js面试题(一)【转发】

    解析: 1.Foo.getName(); //2 1)结果执行的是Foo对象的一个叫做getName()的属性,而1.4.5中的getName都是作为函数存在,所以可以排除1.4.5 2)剩下两个中, ...