參考: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: }
總結下來,要學會分析問題,不能一成不變的只用一個演算法,可能會非常低效。