题目:
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.
For example, given s = "aab", Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.
思路:
1、朴素动态规划法:我开始实现的是最朴素的动态规划法。定义dp[i][j]表示从s[i]到s[j]之间需要分割的最小次数,那么有两种情况:如果从i到j之间的子串本身就是回文串,那么dp[i][j] = 0;否则dp[i][j] = min(dp[i][k] + dp[k + 1][j] + 1), (i <= k < j)。由于在枚举i和j的同时,还要枚举k,所以总体时间复杂度高达O(n^3)。可惜我的代码只通过了28/29个测试样例,过不了最后一个变态的大数据。
2、加记忆的动态规划法:在网上参考了一个很好的思路,可以将时间复杂度降低到O(n^2)。将一个字符串切割成两部分,如果右边是回文子串,那么需要的切点就为:左边的子串切点+1。如果右边非回文,则需要的切点就为:左边子串切点+1+右边子串长度 - 1。这样就将问题分解成了子状态+一个已知状态。我们只需要枚举将字符串分割的切点,取一个最优的解即可计算一个字符串的解,庵后这个解又会作为子集解,用于解决更大规模的问题,直到将整个问题解决。状态转移方程即为:dp[i] = min(dp[i], 1 + dp[j] + var(str[j, i])),其中j为枚举[0, i]之间的所有点,var[str[j, i]]为右子串的解,如果右子串为回文则需要切点为0,否则就是其长度-1。
我们至少需要两重循环,其中一重是用来枚举长度从小到大的字符串,也就是小规模的问题。在解决这个子问题之后我们还需要一重循环来枚举各个分割点将子串分为一个更小子问题+已知状态。在判断右子串是否回文的时候还有一个隐藏的一重循环。但是在判断右子串是否回文的问题上我们可以再次优化。在子串由小变大的过程中,我们可以将每次的结果都保存起来,这样在字符串不断扩大的时候只需要判断两个端点是否相等和子串是否回文即可。这也是一个用空间换时间的方法。此时时间复杂度为O(n^2),顺利通过所有测试数据。
代码:
1、朴素动态规划法(无法通过大数据):
class Solution { public: int minCut(string s) { int length = s.length(); if (length == 0) { return 0; } vector<vector<int>> dp(length, vector<int>(length, INT_MAX)); for (int i = 0; i < length; ++i) { dp[i][i] = 0; } for (int len = 2; len <= length; ++len) { for (int start = 0; start + len <= length; ++start) { int end = start + len - 1; if (isPalindrome(s, start, end)) { dp[start][end] = 0; } else { for (int k = start; k < end; ++k) { dp[start][end] = min(dp[start][end], dp[start][k] + dp[k + 1][end] + 1); } } } } return dp[0][length - 1]; } private: bool isPalindrome(string& s, int left, int right) { int i = left, j = right; while(i < j) { if(s[i] != s[j]) return false; ++i, --j; } return true; } };2、加记忆的动态规划法:
class Solution { public: int minCut(string s) { if(s.length() == 0) { return 0; } int length = s.size(); vector<int> dp(length + 1, INT_MAX); vector<vector<bool>> palin(length, vector<bool>(length, false)); dp[0] = -1; for(int i = 1; i <= s.size(); i++) { for(int j = 0; j < i; j++) { if(s[j] == s[i-1] && (i - j <= 2 || palin[j + 1][i - 2])) { palin[j][i-1] = true; } dp[i] = min(dp[i], dp[j] + (palin[j][i-1] ? 1 : i - j)); } } return dp[length]; } };