1.最长上升子序列(LIS)
一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
方法一:O(n2)算法:
class AscentSequence {
public:
int findLongest(vector<int> A, int n) {
// write code here
vector<int> dp(n,1);
for(int i=0;i<n;i++) //循环n次
for(int j=i-1;j>=0;j--) //比拉力前面所有的元素
if(A[j]<A[i])
dp[i]=max(dp[i],dp[j]+1);
int res = 0;
for(int i=0;i<n;i++)
res = max(res,dp[i]);
return res;
}
};
方法二:O(n*logn)算法
此外,我们用一个变量 end来记录B中最后一个数的下标。
首先,令B[0] = A[0] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2,这时end=0。
然后,考察A[1],可以将A[]有序地放到B里,令B[0] = 1,就是说长度为1的LIS的最小末尾是1,B[0]=2已经被淘汰了,这时 end =1。
接着,A[2] = 5,A[2]>B[0],所以令B[1]=A[2]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[0..1] = 1, 5, end =1。
再来,A[3] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[0..1] = 1, 3, end = 2。
继续,A[4] = 6,比B中最大的数3还要大 ,于是很容易可以推知B[2] = 6, 这时B[0..2] = 1, 3, 6, end = 2。
A[5] = 4,它在3和6之间,于是我们就要把6替换掉,得到B[2] = 4,B[0..2] = 1, 3, 4, end 继续等于2。
A[6] = 8,它很大,比4大。于是继续往B中追加,B[3] = 8, end 变成3了。
A[7] = 9,得到B[4] = 9,此时B是1,3,4,8,9,end是4。
最后一个, A[8] = 7,它在4和8之间,所以我们知道,最新的B[3] =7,B[0..4] = 1, 3, 4, 7, 9, end = 4。
于是我们知道了LIS的长度为 end+1 = 5 。
注意: 这个1,3,4,7,9不是LIS字符串,比如本题例的LIS应该是1,3,4,8,9,7代表的意思是存储4位长度LIS的最小末尾是7, 所以我们的B数组,是存储对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个A[8] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到B[4], 9更新到B[5],得出LIS的长度为6。
然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说, 我们可以使用 二分查找 ,将每一个数字的插入时间优化到 O(logN),于是算法的时间复杂度就降低到了 O(NlogN)。
class AscentSequence {
public:
int findLongest(vector<int> A, int n) {
// write code here
if(A.empty())
return 0;
vector<int> B(n+1,0); //新建数组,用于统计
int len = 1;
B[1] = A[0];
for(int i=1;i<n;i++)
{
if(A[i]>B[len])
B[++len]=A[i]; //如果比末尾元素还大的话,直接添加到尾部
else
{
int left = 1,right = len; //否则,二分查找合适的位置
while(left<right)
{
int mid = (left+right)/2;
if(B[mid]>=A[i])
right = mid;
else
left = mid +1;
}
B[right]=A[i];
}
}
return len;
}
};
2.最长公共子序列(LCS)
给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence)。比如字符串1:BDCABA;字符串2:ABCBDAB
则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA
class LCS {
public:
int findLCS(string A, int n, string B, int m) {
// write code here
vector<vector<int>> lcs(n+1); //设置动态规划数组
for(int i=0;i<n+1;i++)
lcs[i].resize(m+1,0);
for(int i=1;i<n+1;i++)
for(int j=1;j<m+1;j++)
{
if(A[i-1]==B[j-1])
lcs[i][j]=lcs[i-1][j-1]+1;
else
lcs[i][j]=max(lcs[i-1][j],lcs[i][j-1]);
}
return lcs[n][m]; //返回最长公共子序列的值
}
};
3.最长公共子串
注意,这里是子串。子串必须是连着的,而不像子序列!!!这一点至关重要~~~
class LongestSubstring {
public:
int findLongest(string A, int n, string B, int m) {
// write code here
vector<vector<int>> dp(n+1);
for(int i=0;i<n+1;i++)
dp[i].resize(m+1,0);
int res = 0;
for(int i=1;i<n+1;i++)
for(int j =1;j<m+1;j++)
{
if(A[i-1]==B[j-1])
{
dp[i][j]=dp[i-1][j-1]+1; //这里至关重要
res = max(res,dp[i][j]);
}
else
dp[i][j] = 0; //如果不等,直接为0
}
return res;
}
};
4.最小编辑代价
对于两个字符串A和B,我们需要进行插入、删除和修改操作将A串变为B串,定义c0,c1,c2分别为三种操作的代价,请设计一个高效算法,求出将A串变为B串所需要的最少代价。
给定两个字符串A和B,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。
"abc",3,"adc",3,5,3,100
返回:8
动态规划系列问题-最小编辑代价
class MinCost {
public:
int findMinCost(string A, int n, string B, int m, int c0, int c1, int c2) {
// write code here
vector<vector<int>> dp(n+1); //dp[i][j] 的含义是从A[0...i]变化到B[0...j]所需的最小代价
for(int i=0;i<n+1;i++)
dp[i].resize(m+1,0);
for(int j=1;j<m+1;j++) //初始化代价
dp[0][j]=j*c0;
for(int i=1;i<n+1;i++)
dp[i][0] = i*c1;
for(int i=1;i<n+1;i++)
for(int j=1;j<m+1;j++)
{
if(A[i-1]==B[j-1]) //先确定修改方式带来的代价
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = dp[i-1][j-1]+c2;
dp[i][j] = min(dp[i][j],dp[i-1][j]+c1); //对比删除操作
dp[i][j] = min(dp[i][j],dp[i][j-1]+c0); //对比插入操作
}
return dp[n][m];
}
};
5.字符串交错组成
对于三个字符串A,B,C。我们称C由A和B交错组成当且仅当C包含且仅包含A,B中所有字符,且对应的顺序不改变。请编写一个高效算法,判断C串是否由A和B交错组成。
给定三个字符串A,B和C,及他们的长度。请返回一个bool值,代表C是否由A和B交错组成。保证三个串的长度均小于等于100。
"ABC",3,"12C",3,"A12BCC",6
返回:true
class Mixture {
public:
bool chkMixture(string A, int n, string B, int m, string C, int v) {
// write code here
vector<vector<int>> dp(n+1); //dp[i][j] 表示A串的第i个字符之前的串和B串的第j个字符串之前的串组合起来得到当前串C[i+j]
for(int i=0;i<=n;i++)
dp[i].resize(m+1,0);
dp[0][0] = 1;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
{
if(dp[i][j]) //相当于判断前面一步是否可以出厂当前串
{
if(i<n&&A[i]==C[i+j])
dp[i+1][j] = 1;
if(j<m&&B[j]==C[i+j])
dp[i][j+1] = 1;
}
}
return dp[n][m];
}
};