题目1:最长公共连续子序列
1、给定一个query和一个text,均由小写字母组成。要求在text中找出以同样的顺序连续出现在query中的最长连续字母序列的长度。例如, query为“acbac”,text为“acaccbabb”,那么text中的“cba”为最长的连续出现在query中的字母序列,因此,返回结果应该为其长度3。请注意程序效率。
这道题其实就是最长公共连续子序列问题。可以直接用动规来实现。
i=j dp[i][j]=dp[i-1][j-1]+1
i!=j dp[i][j]=0;
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
int getLCS(string &s1, string &s2)
{ int len1 = s1.length(); int len2 = s2.length(); vector<vector< int>> matrix( 2, vector< int>((len2 + 1), 0)); int MaxValue = 0; int flag = 1; int tmp = 0; /* 思路是: 利用两个数组来求解滚粗的方式 */ for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { if(s1[i - 1] == s2[j - 1]) { matrix[flag][j] = matrix[ 1 - flag][j - 1] + 1; if(MaxValue < matrix[flag][j]) { MaxValue = matrix[flag][j]; } } else { matrix[flag][j] = 0; } } flag = 1 - flag; } return MaxValue; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
int getLCS2(string &s1, string &s2) { int len1 = s1.length(); int len2 = s2.length(); vector< int> matrix(len2 + 1, 0); int MaxValue = 0; int flag = 1; int tmp = 0; int old = 0; /* 思路是:用一维数组解决 */ for( int i = 1; i <= len1; i++) { old = 0; for( int j = 1; j <= len2; j++) { tmp = matrix[j]; if(s1[i - 1] == s2[j - 1]) { matrix[j] = old + 1; if(MaxValue < matrix[j]) { MaxValue = matrix[j]; } } else { matrix[j] = 0; } old = tmp; } } return MaxValue; } |
题目2:最长公共子序列
就是所谓的最长公共子序列(LCS)问题。(不要求连续出现的公共子序列)下面给出两种解法,一种利用动规数组,另外一种使用滚粗数组来完成。因为只要求长度,所以可以利用滚粗数组可以实现。
应用公式如下:
i=j dp[i][j]=dp[i-1][j-1]+1
i!=j dp[i][j]=max(dp[i-1][j],dp[i][j-1])
下面是动规数组,直接粗暴,算法复杂度O(n2),空间复杂度O(n2).
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
int LCS(string &query, string &text)
{ int len1 = query.size(); int len2 = text.size(); vector<vector< int>> vec(len1 + 1, vector< int>(len2 + 1, 0)); for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { if(query[i - 1] == text[j - 1]) { vec[i][j] = vec[i - 1][j - 1] + 1; } else { vec[i][j] = vec[i][j - 1] > vec[i - 1][j] ? vec[i][j - 1] : vec[i - 1][j]; } } } // PrintLCS(vec,query,text,len1,len2); return vec[len1][len2]; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
进一步优化空间 假如只求长度,我们只需要最后一行的结果,因此我们只需要两行的数组。滚粗数组 int LCS2(string &query, string &text) { int len1 = query.size(); int len2 = text.size(); vector< vector< int> > vec( 2, vector< int>(len2 + 1, 0)); int flag = 1; for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { if(query[i - 1] == text[j - 1]) { vec[flag][j] = vec[ 1 - flag][j - 1] + 1; } else { vec[flag][j] = vec[flag][j - 1] >= vec[ 1 - flag][j] ? vec[flag][j - 1] : vec[ 1 - flag][j]; } } flag = 1 - flag; } return vec[ 1 - flag][len2]; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//再进一步优化,采用一个整数来记录上方的元素 int getLcsLength(string &query, string &text) { int len1 = query.size(); int len2 = text.size(); vector< int > dp(len2 + 1, 0); int old = 0; int tmp = 0; for( int i = 1; i <= len1; i++) { old = 0; for( int j = 1; j <= len2; j++) { tmp = dp[j]; if(query[i - 1] == text[j - 1]) { dp[j] = old + 1; } else { if(dp[j] < dp[j - 1]) dp[j] = dp[j - 1]; } old = tmp; } } return dp[len2]; } |
4、我们有时候需要把最长公共子序列打印出来,我们采用递归的方式进行打印。具体实现如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//根据动规矩阵 和递归打印最长公共字符串 void PrintLCS(vector<vector< int>> &vec, string &query, string &text, int i, int j) { if(i == 0 || j == 0) return ; if(query[i - 1] == text[j - 1]) { PrintLCS(vec, query, text, i - 1, j - 1); //从后面开始递归回状态 cout << query[i - 1]; } else if(vec[i - 1][j] >= vec[i][j - 1]) // { PrintLCS(vec, query, text, i - 1, j); } else { PrintLCS(vec, query, text, i, j - 1); } } |
这个时候我们需要新建一个辅助数组,记录公共子序列的走向。我们采用递归回溯的方法来实现搜索所有可能的路径。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
//下面我们来实现把所有的最长公共子序列都打印出来试试 vector< char> result; void Display_Lcs( int i, int j, string &x, vector<vector< int> > &mvec, int current_len, int lcs_max_len) { if(i == 0 || j == 0) { if(result.size() != lcs_max_len) { result.clear(); return; } for( int s = 0; s < lcs_max_len; s++) { cout << result[lcs_max_len - s - 1]; } cout << endl; result.clear(); return; } if(mvec[i][j] == 1) { current_len--; result.push_back(x[i - 1]); Display_Lcs(i - 1, j - 1, x, mvec, current_len - 1, lcs_max_len); } else { if(mvec[i][j] == 2) { Display_Lcs(i - 1, j, x, mvec, current_len - 1, lcs_max_len); } else { if(mvec[i][j] == 3) { Display_Lcs(i, j - 1, x, mvec, current_len, lcs_max_len); } else { Display_Lcs(i, j - 1, x, mvec, current_len, lcs_max_len); Display_Lcs(i - 1, j, x, mvec, current_len - 1, lcs_max_len); } } } } int LCS4(string &query, string &text) { int len1 = query.size(); int len2 = text.size(); vector<vector< int>> vec(len1 + 1, vector< int>(len2 + 1, 0)); vector<vector< int> >mvec(len1 + 1, vector< int>(len2 + 1, 0)); for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { if(query[i - 1] == text[j - 1]) { vec[i][j] = vec[i - 1][j - 1] + 1; mvec[i][j] = 1; } else if(vec[i - 1][j] > vec[i][j - 1]) { vec[i][j] = vec[i - 1][j]; mvec[i][j] = 2; } else if(vec[i - 1][j] < vec[i][j - 1]) { vec[i][j] = vec[i][j - 1]; mvec[i][j] = 3; } else { vec[i][j] = vec[i][j - 1]; mvec[i][j] = 4; } } int m; } // PrintLCS(vec,query,text,len1,len2); Display_Lcs( len1, len2, query, mvec, vec[len1][len2], vec[len1][len2] ); return vec[len1][len2]; } |
题目3:最长递增子序列
最长递增子序列LIS(Longest Increasing Subsequence):
给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4。
分析:其实此LIS问题可以转换成最长公子序列问题,为什么呢?
原数组为A {5, 6, 7, 1, 2, 8}
排序后:A‘{1, 2, 5, 6, 7, 8}
因为,原数组A的子序列顺序保持不变,而且排序后A‘本身就是递增的,这样,就保证了两序列的最长公共子序列的递增特性。如此,若想求数组A的最长递增子序列,其实就是求数组A与它的排序数组A‘的最长公共子序列。
此外,本题也可以使用动态规划来求解,读者可以继续思考。
下面我们直接用动态规划的思想来解决:
设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),
则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),
找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),
找出最大值即为最大递增子序列。时间复杂度为O(N^2)。
例如给定的数组为{5,6,7,1,2,8},则L(0)=1, L(1)=2, L(2)=3, L(3)=1, L(4)=2, L(5)=4。
所以该数组最长递增子序列长度为4,序列为{5,6,7,8}。
这个解决的思想就是用到了替换的思想,每次只保留最后末尾的值来显示最长递增。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
int getLIS(vector<
int> &s)
{ int len = s.size(); vector< int> longest(len, 1); int Max = 1; for( int i = 1; i < len; i++) { for( int j = 0; j < i; j++) { if(s[j] < s[i] && longest[i] < longest[j] + 1) { longest[i] = longest[j] + 1; if(Max < longest[i]) { Max = longest[i]; } } } } return Max; } |
这种解法用得比较巧妙。用到了二叉搜索树的概念。
基本思路:
新建一个O(N) 的辅助数组
需要遍历所有的dp[0]~dp[i-1]。而这也是优化的关键点,即如何减少每次对前面i项的遍历。
试想一下,如果某个val[j]<val[i],使得dp[i]更新为dp[j]+1,那么所有小于dp[j]的项其实无需再考虑。
对于长度相同的项,只记录下末尾数最小的项,因为如果这一项都无法满足小于val[i],其他项就更不可能满足这个条件。
根据第二种方法,我们只需要建立一个len数组,其中len[k]表示当前长度为k的递增子序列最小的末尾数是len[k]。
考虑第i项时,只需二分len数组已存在的下标范围,找到满足len[k]<val[i]且k最大的位置,并用val[i]更新len[k+1],
如果len[k+1]在更新前没赋值过,就将len数组的最大下标增一,最后的结果就是len数组的最大下标。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
int BiSearch(vector<
int > &Bitree,
int len,
int value)
{ int left = 0, right = len - 1; int mid; while(left <= right) { mid = left + (right - left) / 2; if(Bitree[mid] > value) right = mid - 1; else if(Bitree[mid] < value) left = mid + 1; else return mid; } return left; } int searchLIS(vector< int> &arr) { int len = 1; int size = arr.size(); vector< int> Bitree(size, 0); Bitree[ 0] = arr[ 0]; int pos = 0; for( int i = 1; i < size; ++i) { if(arr[i] > Bitree[len - 1]) //如果大于Bitree中最大的元素,则直接插入到B数组尾 { Bitree[len] = arr[i]; ++len; } else { pos = BiSearch(Bitree, len, arr[i]); //二分查找需要插入的位置 Bitree[pos] = arr[i]; } } return len; } |
题目三:最长回文子串
这道题就是求一个字符串里面的最长的一个回文子串。这里并没有用到动规的方法。
而是直接采用搜索的方法。分别对长度为奇数和长度为偶数的回文子串进行搜索。
搜索到最长的返回。
代码如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
int MaxPromelineSubSuqueue(string &str)
{ int len = str.length(); int maxlength = 0; int start = 0; //查找长度为奇数的最长回文子串 for( int i = 1; i < len; i++) { int j = i - 1, k = i + 1; while(j >= 0 && k < len && str.at(j) == str.at(k)) { if(k - j + 1 > maxlength) { maxlength = k - j + 1; start = j; } j--; k++; } } //查找长度为偶数的最长回文子串 for( int i = 0; i < len; i++) { int j = i; int k = i + 1; while(j >= 0 && k < len && str.at(j) == str.at(k)) { if(k - j + 1 > maxlength) { maxlength = k - j + 1; start = j; } j--; k++; } } if(maxlength > 0) { string s = str.substr(start, maxlength); cout << s << endl; } return maxlength; } |
题目四:最长不重复子串
根据题目的意思是:从一个字符串中找到一个连续子串,该子串任何两个字符不能相同,求子串的最大长度并输出最长不重复子串。
例如输入abcbef,输出cbef
这道题有点hash表的味道。新建一个位数256的数组,对字符进行映射。然后逐个去找最长的不重复的子串。
基本思路如下:
O(N)的算法,具体思路如下:
以abcbef这个串为例,用一个数组pos记录每个元素曾出现的下标,初始化为-1。从s[0]开始,依次考察每个字符,例如pos['a'] == -1,说明a还未出现过,令pos['a'] = 0,视为将‘a’加入当前串,同时长度+1,同理pos['b'] = 1,pos['c'] = 2,考察s[3],pos['b'] != -1,说明'b'在前面已经出现过了,此时可得到一个不重复串"abc",刷新当前的最大长度,然后更新pos['b']及起始串位置。
过程如下:
1、建一个256个单元的数组,每一个单元代表一个字符,数组中保存上次该字符出现的位置;
2、依次读入字符串,同时维护数组的值;
3、如果遇到冲突了,考察当前起始串位置到冲突字符的长度,如果大于当前最大长度,
则更新当前最大长度并维护冲突字符的位置,更新起始串位置,继续第二步。
具体代码如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
int GetMaxSubStr(string &str ) { int hash[ 256]; //hash记录每个字符的出现位置 for ( int i = 0; i < 256; i++) //初始化 { hash[i] = - 1; } int CurrentStart = 0, CurrentLength = 0, MaxStart = 0, MaxEnd = 0; int strLen = str.length(); for( int i = 0; i < strLen; i++) { if(CurrentStart > hash[str[i]]) //如果没有重复 { hash[str[i]] = i; } else { CurrentLength = i - CurrentStart; //当前长度 if(CurrentLength > MaxEnd - MaxStart) //如果当前长度最长 { MaxEnd = i; MaxStart = CurrentStart; } CurrentStart = hash[str[i]] + 1; //更新当前最长的起点 hash[str[i]] = i; //更新字符出现的位置 } } if (MaxEnd == 0) //没有重复字符,返回源串 { cout << str << endl; return strLen; } CurrentLength = strLen - CurrentStart; //当前长度 if(CurrentLength > MaxEnd - MaxStart) //如果当前长度最长 { MaxEnd = strLen; MaxStart = CurrentStart; } int MaxLength = MaxEnd - MaxStart; string s = str.substr(MaxStart, MaxLength); cout << s << endl; return MaxLength; } |
题目五:最大子序列和
这道题即不说,已经见过很多次了。基本思想是:抛弃没有贡献的前段,重新开始。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
int MaxSumSubSequeue(vector<
int> &arr)
{ int len = arr.size(); int sum = 0; int Max = 0; for( int i = 0; i < len; i++) { if(sum + arr[i] > 0) { sum += arr[i]; } else { sum = 0; } if(sum > Max) { Max = sum; } } return Max; } |
题目六:字符串编辑距离
这道题是动规的一个非常经典的题目,简直是屡见不鲜了。掌握动规的基本思想即可解决这个问题。
基本解法如下:
这是一个经典的动规问题
以str1c和str2d,从最后一个字符串来看
如果c=d则有 dp[i][j]=dp[i-1][j-1];
如果是
插入 dp[i][j]=dp[i][j-1]+1
删除 dp[i][j]=dp[i-1][j]+1
替换 dp[i][j]=dp[i-1][j-1]+1
代码如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
int EditDistance(string &s1, string &s2)
{ int len1 = s1.length(); int len2 = s2.length(); vector<vector< int>> dp(len1 + 1, vector< int>(len2 + 1, 0)); for( int i = 1; i <= len1; ++i) { dp[i][ 0] = i; } for( int i = 1; i <= len2; ++i) { dp[ 0][i] = i; } for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { if(s1[i - 1] == s2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { int mn = min(dp[i][j - 1], dp[i - 1][j]); dp[i][j] = min(dp[i - 1][j - 1], mn) + 1; } } } return dp[len1][len2]; } /* 利用滚粗数组实现 */ int EditDistance2(string &s1, string &s2) { if(s1.length() > s2.length()) return EditDistance2(s2, s1); int len1 = s1.length(); int len2 = s2.length(); vector< int> dp(len2 + 1, 0); int upperleft = 0; int old = 0; for( int i = 1; i <= len2; i++) { dp[i] = i; } for( int i = 1; i <= len1; i++) { old = i; for( int j = 1; j <= len2; j++) { upperleft = dp[j]; if(s1[i - 1] == s2[j - 1]) { dp[j] = old; } else { dp[j] = min(dp[j], min(dp[j - 1], old)) + 1; } old = upperleft; } } return dp[len2]; } |
题目七:Word Break &&Word Break II
这个题目的意思是说,给你一个字符串,和一个字典。看看能不能用字典的元素组成这个字符串。题目II需要把所有可能都找出来。
我们先来解决题目I:
对于这道题我们首先想到的是用深度搜索来解决这个问题,意思就是说,用for循环和递归,一个一个的查找有没有合适的分割,有合适的分割就进行递归查找其他的部分是否由合适的分割。但是这种深度搜索的复杂度太高了。
然后我们考虑的是用动态规划的方法来实现。采用动规的方法,需要总结出这个动规的初始状态和动规的转移方程。
设状态为f(i) ,表示s[0,i]是否可以分词(二维状态,我们采用bool型来表示)。显而易见的一个规律是,0到i直接,存在任何一个s[0,j]为true,并且s[i,j]属于字典,则为正,假如s[0,j]和s[i,j]属于字典这两个任何一个为false ,则为false。因此我们有:
f(i)=any_of(f(j)&&s[j+1,i]属于dict),0<=j<i。
下面是两种解法的代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
class Solution1 { public: bool wordBreak(string s, unordered_set<string> &dict) { //这是一个蛮力解决的方法。 深度搜索 int len = s.length(); if(len < 1) return true; bool flag = false; for( int i = 1; i <= len; i++) { string tmpstr = s.substr( 0, i); if(dict.find(tmpstr) != dict.end()) { if(tmpstr.length() == len) return true; flag = wordBreak(s.substr(i), dict); } if(flag == true) { return true; } } } }; class Solution { public: bool wordBreak(string s, unordered_set<string> &dict) { int len = s.length(); vector< bool> bvec(len + 1, false); bvec[ 0] = true; for( int i = 1; i <= len; i++) { for( int j = 0; j < i; j++) { if(bvec[j] && (dict.find(s.substr(j, i - j)) != dict.end())) { bvec[i] = true; break; } } } return bvec[len]; } }; |
解决了问题I,下面我们来解决问题II。要把所有的可能性求出来,我们需要再问题I的基础上完成。基本思想是:定义一个二维的数组来,代替一维的数组,毕竟只有二维的数组才能指明方向。 然后用深度搜索的方法,以及回溯剪枝的形式把这些可能性给遍历回来,虽然复杂度有点高,不过要求所有可能性那也是没有办法的了。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
class Solution
{ public: vector<string> wordBreak(string s, unordered_set<string> &dict) { int len = s.size(); vector<vector< bool>> prev(len + 1, vector< bool>(len, false)); vector< bool> dp(len + 1, false); dp[ 0] = true; for( int i = 1; i <= len; i++) { for( int j = 0; j < i; j++) { if(dp[j] && dict.find(s.substr(j, i - j)) != dict.end()) { dp[i] = true; prev[i][j] = true; } } } vector<string> path; vector<string> ret; gen_path(s, s.length(), prev, path, ret); return ret; } void gen_path( const string &s, int cur, const vector<vector< bool>> &prev, vector<string> &path, vector<string> &ret) { if(cur == 0) { string tmp; int i = path.size() - 1; for(; i > 0; i--) { tmp += path[i] + " "; } tmp += path[ 0]; ret.push_back(tmp); } for( int i = 0; i < s.length(); i++) { if(prev[cur][i]) { path.push_back(s.substr(i, cur - i)); gen_path(s, i, prev, path, ret); path.pop_back(); } } } }; |
问题八:Scarmble String
这道题的题目有点难以理解。相当费劲啊。
Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty
substrings recursively.
Below is one possible representation of s1 = "great":
great
/ \
gr eat
/ \ / \
g r e at
/ \
a t
To scramble the string, we may choose any non-leaf node and swap its two children.
For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat".
rgeat
/ \
rg eat
/ \ / \
r g e at
/ \
a t
We say that "rgeat" is a scrambled string of "great".
Similarly, if we continue to swap the children of nodes "eat" and "at",
it produces a scrambled string "rgtae".
rgtae
/ \
rg tae
/ \ / \
r g ta e
/ \
t a
We say that "rgtae" is a scrambled string of "great".
Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.
其实这个题目简单的说就是:
s1和s2是scramble的话,那么必然存在一个在s1上的长度l1,将s1分成s11和s12两段,同样有s21和s22。那么要么s11和s21是scramble的并且s12和s22是scramble的;要么s11和s22是scramble的并且s12和s21是scramble的。
基本思路:其实上面就说了基本思路了。这道题原本是可以用动规来做的。不过由于不是很理解动规的做法,无奈之下只能用深度搜索的方法来做了。
深度搜索的复杂度比较高,因此我们需要采用很多的剪枝判断条件。也用到了一些优化的小技巧来实现。
具体代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
class Solution
{ public: bool isScramble(string s1, string s2) { int len1 = s1.length(); int len2 = s2.length(); if(s1 == s2) return true; if(len1 != len2) return false; int A[ 26] = { 0}; for( int i = 0; i < len1; i++) { A[s1[i] - 'a']++; } for( int i = 0; i < len2; i++) { A[s2[i] - 'a']--; } for( int i = 0; i < 26; i++) { if(A[i] != 0) return false; } bool result = false; for( int i = 1 ; i < s1.size() ; ++i) { result = isScramble(s1.substr( 0, i) , s2.substr( 0, i)) && isScramble(s1.substr(i) , s2.substr(i)); if(result) return true; result = isScramble(s1.substr( 0, i) , s2.substr(s1.size() - i, i)) && isScramble(s1.substr(i) , s2.substr( 0 , s1.size() - i)); if(result) return true; } return false; } }; |
题目九:Triage
题目
[
[2]
[3,4]
[6,5,7]
[4,1,8,3]
]
由上面往下面走,求路径最小的。
这个一道经典的动规题目
首先求动规的转移方程
dp[i][j]=min(dp[i][j+1],dp[i][j+1])+1;
里面有个小技巧就是从倒数第二行开始计算。重复利用原来的数组。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Solution
{ public: int mininumTatal(vector<vector< int> > &triangle) { int len = triangle.size(); for( int i = len - 2; i >= 0; i--) { for( int j = 0; j < triangle[i].size(); j++) { triangle[i][j] += min(triangle[i + 1][j], triangle[i + 1][j + 1]); } } return triangle[ 0][ 0]; } }; |
题目十:Palindrome Partitioning II
这道题的意思是:给你一个字符串,用最小的刀切割字符串,使得字符串要么是回文字符串,要么是单个字符(没有回文的情况下)。
For example, given s = "aab",
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.
这道题我们第一眼看上是可以用深度搜索来解决问题的。
我们当然是从整个字符串是否是回文开始判断,其实for循环加上递归和剪枝就可以完成了。
但是用深度搜索复杂度比较高,下面我们用动态规划来实现:
动规的基本套路就是:初始化状态+状态转移方程。
首先我们来看看思路,我们先假设所有的字符都需要切割,因此有最大的切割术,数组n值为-1,数组n-1值为0,这样一直下去。
然后来看看状态转移方程:
我们还是需要一个辅助的数组dp[i][j],这个数组主要是用来判断回文用的,因为回文字符串有相应规律,其判断条件是[s[i]==s[j]&&(j-i>2||dp[i+1][j-1])],然后,用二级for循环进行计算。
具体实现的代码如下:
深度搜索的代码如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* 下面是一个剪枝回溯的方法:*/ bool isPalindrome(string &s, int start, int end) { while(start < end) { if(s[start++] != s[end--]) return false; } return true; } void DFS(string &s, int start, int depth, int &min) { if(start == s.size()) { if(min > depth - 1) min = depth - 1; return ; } for( int i = s.size() - 1; i >= start; i--) { if(isPalindrome(s, start, i)) { DFS(s, i + 1, depth + 1, min); } } } int minCut1(string s) { int min = INT_MAX; DFS(s, 0, 0, min); return min; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
int minCut(string s)
{ int len = s.size(); vector<vector< bool> >dp(len, vector< bool>(len, false)); vector< int> f(len + 1, 0); for( int i = 0; i <= len; i++) { f[i] = len - i - 1; } for( int i = len - 1; i >= 0; i--) { for( int j = i; j < len; j++) { if(s[i] == s[j] && (j - i > 2 || dp[i + 1][j - 1])) { dp[i][j] = true; f[i] = min(f[i], f[j] + 1); } } } return f[ 0]; } |
题目十一:Interleaving String
这道题的意思是说,给你两个字符,然后有一个第三个字符串,问这个第三个字符是否有前面两个字符的唯一两个子序列呢。
这是一道典型动规题目。
首先我们判断两个字符串的长度之和等于第三个字符串。
然后我们利用动规的方法来实现实现解决:
我们先来看看初始化状态:
f[i][0]=f[i-1][0]&&(s1[i-1]==s3[i-1]);
f[0][i]=f[0][i-1]&&(s2[i-1]==s3[i-1]);
我们从最后一个字符串来考虑问题
如果 s1[i-1]==s3[i+j-1]相同,前面的状态f[i-1][j]是true。
如果 s2[j-1]==s3[i+j-1]相同,前面的状态f[i][j-1]是true。
两个其中一个正确即可,实现状态转移。
代码实现如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Solution
{ public: bool isInterleave(string s1, string s2, string s3) { int len1 = s1.size(); int len2 = s2.size(); int len3 = s3.size(); if(len3 != len1 + len2) return false; vector<vector< bool>> f(len1 + 1, vector< bool>(len2 + 1, true)); //先求初始化状态 for( int i = 1; i <= len1; i++) { f[i][ 0] = f[i - 1][ 0] && (s1[i - 1] == s3[i - 1]); } for( int i = 1; i <= len2; ++i) { f[ 0][i] = f[ 0][i - 1] && (s2[i - 1] == s3[i - 1]); } //根据动态规划的状态转移方程求解结果 for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { f[i][j] = (s1[i - 1] == s3[i + j - 1] && f[i - 1][j]) || (s2[j - 1] == s3[i + j - 1] && f[i][j - 1]); } } return f[len1][len2]; } }; |
题目十二:
Distinct Subsequences
Given a string S and a string T, count the number of distinct subsequences of T in S.
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE"
is a subsequence of "ABCDE"
while "AEC"
is not).
Here is an example:
S = "rabbbit"
, T = "rabbit"
Return 3
.
这也是一道经典的动态规划的问题。
还是根据动规的基本规律来搞,
首先是初始化状态 dp[i][0]=1;
然后我们用两个for循环来实现,如果S[i-1]==T[j-1]我们可以知道i,j的状态是有i-1,j-1或者i-1,j转换过来,所以对他们求和。
如果不等的话,直接去掉S的一位dp[i][j]=dp[i-1][j](即是忽略这一位。)
我们直接上代码吧
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Solution
{ public: int numDistinct(string S, string T) { int len1 = S.length(); int len2 = T.length(); vector<vector< int >> dp(len1 + 1, vector< int>(len2 + 1, 0)); //初始状态 for( int i = 0; i <= len1; i++) { dp[i][ 0] = 1; } for( int i = 1; i <= len1; i++) { for( int j = 1; j <= len2; j++) { if(S[i - 1] == T[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; } else { dp[i][j] = dp[i - 1][j]; } } } return dp[len1][len2]; } }; |
题目十三:
Best Time to Buy and Sell Stock III
这个题目的意思是:
两次买卖,使得利润最大
我们可以这样考虑:
新建两个数组,一个数组表示从0到i天的最大利润,第二个数组表示第i天到n-1天的最大利润,
从左往右考虑,f1max=f[i]-Min ,但是这个要考虑之前最大值。
从右往左考虑,f2max=Max-f[i] ,但是要考虑之前的最大值。
根据上面两个数组,我们再扫描一下数组,求出f1+f2的数组的最大值。
代码实现如下所示:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
class Solution { public: int maxProfit(vector< int> &prices) { int len = prices.size(); if(len < 2) return 0; vector< int> f1(len, 0); vector< int> f2(len, 0); int MinPrices = prices[ 0]; int MaxPrices = prices[len - 1]; for( int i = 1; i < len; i++) { if(prices[i] < MinPrices) { MinPrices = prices[i]; } f1[i] = max(prices[i] - MinPrices, f1[i - 1]); } for( int i = len - 2; i >= 0; i--) { if(prices[i] > MaxPrices) { MaxPrices = prices[i]; } f2[i] = max(MaxPrices - prices[i], f2[i + 1]); } int Max = f1[ 0] + f2[ 0]; for( int i = 1; i < len; i++) { if(Max < f1[i] + f2[i]) { Max = f1[i] + f2[i]; } } return Max; } }; |
题目十四:
Decode Ways
A message containing letters from A-Z
is being encoded to numbers using the following mapping:
'A' -> 1
'B' -> 2
...
'Z' -> 26
Given an encoded message containing digits, determine the total number of ways to decode it.
For example,
Given encoded message "12"
, it could be decoded as "AB"
(1 2) or "L"
(12).
The number of ways decoding "12"
is 2.
这道题其实就是变相的斐波拉契数列。只不过这道题是有约束条件的。很明显每两个数可以分别拆成两种方式,一种是分别一个一个单,以及两位数。特别要注意的是'0',以及大于'7'的数字。这种动规的题目,需要从后面
代码如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class Solution
{ public: int numDecodings(string s) { if(s.length() == 0) return 0; int len = s.length(); if(len < 1) return 0; vector< int> arr(len, 0); int i = len - 1; arr[i] = (s.at(i) == '0') ? 0 : 1; //从后面开始算起 i--; //从倒数第二位开始算起 while(i >= 0) { if(s.at(i) == '0') //下一位是10 { arr[i] = 0; } else if(s.at(i) == '1' || s.at(i) == '2' && s.at(i + 1) <= '6') //可以分解成两种可能 { if(i == len - 2) { arr[i] = arr[i + 1] + 1; } else { arr[i] = arr[i + 1] + arr[i + 2]; } } else //只有一种可能分解 { arr[i] = arr[i + 1]; } i--; } return arr[ 0]; } }; |