Leetcode: Palindrome Partitioning II
参考:http://www.cppblog.com/wicbnu/archive/2013/03/18/198565.html
我太喜欢用dfs和回溯法了,但是这些暴力的方法加上剪枝之后复杂度依然是很高,显然不能达到题目的要求。
这个时候应该考虑动态规划,并且要复杂度尽量接近O(n^2)的算法。
下面这个方法更加简洁:自长到短找到回文串后,往后dfs,并记录递归深度表示并更新最小划分数。http://fisherlei.blogspot.com/2013/03/leetcode-palindrome-partitioning-ii.html
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.
题解:
类似矩阵连乘的动归思路。
dp[i][j]=min(dp[i][k]+dp[k+1][j]+1), i<=k<j.
但是用这个方程的时间是O(n^3),简化dp[i][j]为dp[i],表示从0到i的minCut.
dp[i]=min(dp[k]+1, dp[k]+i-k), 0<=k<i.
(s[k+1, i]是回文串) (s[k+1, i]不是回文串)
具体代码参见上述链接。
值得注意的是,计算是否为回文数的过程中也要用记忆化搜索才能减少重复比较的次数,it's smart~
MY CODE:
1 // 2 // ParlindromePartitioningII.cpp 3 // SJMcode 4 // 5 // Created by Jiamei Shuai on 13-8-31. 6 // Copyright (c) 2013年 Jiamei Shuai. All rights reserved. 7 // 8 9 #include <vector> 10 #include <iostream> 11 #include <string.h> 12 #include <assert.h> 13 using namespace std; 14 15 // 两处优化: 16 // 1.已经计算过的区间的最短划分次数用map纪录 17 // 2.回文串的判断结果也要用map记录 18 19 class Solution{ 20 public: 21 int *minCutMat; 22 vector<vector<int> > map; 23 24 int IsPalindrome(string &s, int i, int j) 25 { 26 if(i>j) return false; 27 if(map[i][j]!= -1) 28 return map[i][j]; 29 if(i==j) 30 return map[i][j]=1; 31 32 if(s[i]!=s[j]) 33 return map[i][j]=0; 34 else{ 35 if(j-i==1) 36 return map[i][j]=1; 37 else 38 return map[i][j]=IsPalindrome(s,i+1,j-1); 39 } 40 } 41 42 43 int minCut(string s) // 动态规划 d[i] = min{d[k]+1, d[k]+i-k}, 0<=k<i 44 { 45 int n = (int)s.length(); 46 if(n==0||n==1) 47 return 0; 48 49 vector<int> min, vtmp; 50 min.clear();vtmp.clear();map.clear(); 51 for(int i=0; i<s.size(); i++) 52 { 53 min.push_back(0); 54 vtmp.push_back(-1); 55 } 56 for(int i=0; i<s.size(); i++) 57 map.push_back(vtmp); 58 59 int tmp, ans; 60 for(int inter = 1; inter<n; inter++) 61 { 62 if(IsPalindrome(s, 0, inter)) 63 min[inter]=0; 64 else{ 65 ans = n+1; 66 for(int k = 0; k < inter; k++) 67 { 68 if(IsPalindrome(s, k+1, inter)) 69 tmp = min[k]+1; 70 else 71 tmp = min[k] + inter - k; 72 if(tmp < ans) 73 ans = tmp; 74 } 75 min[inter] = ans; 76 } 77 } 78 return min[n-1]; 79 } 80 81 82 83 // 较复杂的算法用dfs或者回溯法都太慢了,加上了所有的剪枝策略还是会超时 84 // 这种情况大多数都应该使用动态规划,要多总结,少犯错误。 85 86 int minCut2(string s) // 总是超时,复杂度太高 87 //这个方法相当于类似矩阵链乘的算法,dp[i][j] = min(dp[i][k]+dp[k+1][j]), i<=k<j,复杂度是O(n^3) 88 //可以简化dp[i][j]为dp[i],表示从0到i的minCut 89 { 90 int minCutNum = (int)s.size(); 91 int len = (int)s.size(); 92 93 minCutMat = new int[len*len]; // 注意new int[]而不是() 94 memset(minCutMat, -1, len*len*sizeof(int)); 95 96 vector<int> vtmp; 97 vtmp.clear();map.clear(); 98 for(int i=0; i<s.size(); i++) 99 vtmp.push_back(-1); 100 for(int i=0; i<s.size(); i++) 101 map.push_back(vtmp); 102 103 // Notice: if the string need no split and itself a palindrome, how to handle it? 注意细节 104 if(IsPalindrome(s, 0, len-1)) return 0; 105 106 split(s, 0, len-1, minCutNum); 107 108 delete []minCutMat; 109 110 return minCutNum; 111 } 112 113 int split(string &s, int begin, int end, int &minCutNum) 114 { 115 if(begin == end) return 0; 116 117 if(IsPalindrome(s, begin, end)) return 0; 118 119 int minCurrentSplit = (int)s.size(); 120 int left,right; 121 122 for(int i = begin; i < end; i++) 123 { 124 assert(begin*s.size()+i < s.size()*s.size()); 125 assert(begin*s.size()+i < s.size()*s.size()); 126 if(minCutMat[begin*s.size()+i] >= 0) 127 left = minCutMat[begin*s.size()+i]; 128 else 129 { 130 left = split(s, begin, i, minCutNum); 131 minCutMat[begin*s.size()+i] = left; 132 } 133 if(left >= minCutNum) { return 1<<20;} 134 135 if(minCutMat[(i+1)*s.size()+end] >= 0) 136 right = minCutMat[(i+1)*s.size()+end]; 137 else 138 { 139 right = split(s, i+1, end, minCutNum); 140 minCutMat[(i+1)*s.size()+end] = right; 141 } 142 if(right >= minCutNum) return 1<<20; 143 144 int tmp = left + 1 + right; 145 146 minCurrentSplit = min(tmp, minCurrentSplit); 147 148 if(begin == 0 && end == s.size()-1) // outer loop 149 minCutNum = min(tmp, minCutNum); 150 } 151 return minCurrentSplit; 152 } 153 154 }; 155 156 int main() 157 { 158 Solution sln; 159 cout << sln.minCut("apjesgpsxoeiokmqmfgvjslcjukbqxpsobyhjpbgdfruqdkeiszrlmtwgfxyfostpqczidfljwfbbrflkgdvtytbgqalguewnhvvmcgxboycffopmtmhtfizxkmeftcucxpobxmelmjtuzigsxnncxpaibgpuijwhankxbplpyejxmrrjgeoevqozwdtgospohznkoyzocjlracchjqnggbfeebmuvbicbvmpuleywrpzwsihivnrwtxcukwplgtobhgxukwrdlszfaiqxwjvrgxnsveedxseeyeykarqnjrtlaliyudpacctzizcftjlunlgnfwcqqxcqikocqffsjyurzwysfjmswvhbrmshjuzsgpwyubtfbnwajuvrfhlccvfwhxfqthkcwhatktymgxostjlztwdxritygbrbibdgkezvzajizxasjnrcjwzdfvdnwwqeyumkamhzoqhnqjfzwzbixclcxqrtniznemxeahfozp"); 160 161 return 0; 162 }
附上更简洁的算法:
1: int minCut(string s) { 2: int len = s.size(); 3: int D[len+1]; 4: bool P[len][len]; 5: //the worst case is cutting by each char 6: for(int i = 0; i <= len; i++) 7: D[i] = len-i; 8: for(int i = 0; i < len; i++) 9: for(int j = 0; j < len; j++) 10: P[i][j] = false; 11: for(int i = len-1; i >= 0; i--){ 12: for(int j = i; j < len; j++){ 13: if(s[i] == s[j] && (j-i<2 || P[i+1][j-1])){ 14: P[i][j] = true; 15: D[i] = min(D[i],D[j+1]+1); 16: } 17: } 18: } 19: return D[0]-1; 20: }
以及使用回溯+剪枝的方法:
1: int minCut(string s) { 2: int min = INT_MAX; 3: DFS(s, 0, 0, min); 4: return min; 5: } 6: void DFS(string &s, int start, int depth, int& min) 7: { 8: if(start == s.size()) 9: { 10: if(min> depth-1) 11: min = depth-1; 12: return; 13: } 14: for(int i = s.size()-1; i>=start; i--) //find the biggest palindrome first 15: { 16: if(isPalindrome(s, start, i)) 17: { 18: DFS(s, i+1, depth+1, min); 19: } 20: 21: 22: } 23: } 24: bool isPalindrome(string &s, int start, int end) 25: { 26: while(start< end) 27: { 28: if(s[start] != s[end]) 29: return false; 30: start++; end--; 31: } 32: return true; 33: }
总结下来,要学会分析问题,不能一成不变的只用一个算法,可能会非常低效。
你问我生命中还有什么可追寻?