result[0][i]=0;
}
for(inti=1;i<=length1;i++){
for(intj=1;j<=length2;j++){
if(str1.charAt(i-1)==str2.charAt(j-1))
result[i][j]=result[i-1][j-1]+1;
else
result[i][j]=result[i-1][j]>result[i][j-1]?
result[i-1][j]:
result[i][j-1];
}
}
System.out.println(result[length1][length2]);
带有详细注释的代码可以在
Pkuacm2250Compromise动态规划题目总结(六)
这个也是求最长公共字串,只是相比CommonSubsequence需要记录最长公共字串的构成,此时箭头的标记就用上了,在程序中,用opt[][]存放标记,0表示朝向左上方,1表示指向上,-1表示指向左。
result[][]存放当前最大字串长度。
在求最优解时,顺着箭头从后向前寻找公共字串的序号,记录下来,输出即可。
该算法在算法导论中有详细的讲解。
带有详细注释的代码可以在
Pkuacm1159Palindrome动态规划题目总结(七)
给一个字符串,求这个字符串最少增加几个字符能变成回文,如Ab3bd可以增加2个字符变为回文:
Adb3bdA。
通过这样的结论可以和最长公共子串联系起来(未证明):
S和S'(注:
S'是S的反串)的最长公共子串其实一定是回文的。
这样我们就可以借助lcs来解决该题,即用s的长度减去lcs的值即可。
核心的Java代码为:
total-LCS(string,newStringBuffer(string).reverse().toString());
//函数LCS返回两个string的lcs的长度
带有详细注释的代码可以在
Pkuacm1080HummanGeneFunction动态规划题目总结(八)
这是一道比较经典的DP,两串基因序列包含A、C、G、T,每两个字母间的匹配都会产生一个相似值,求基因序列(字符串)匹配的最大值。
这题有点像求最长公共子序列。
只不过把求最大长度改成了求最大的匹配值。
用二维数组opt[i][j]记录字符串a中的前i个字符与字符串b中的前j个字符匹配所产生的最大值。
假如已知AG和GT的最大匹配值,AGT和GT的最大匹配值,AG和GTT的最大匹配值,求AGT和GTT的最大匹配值,这个值是AG和GT的最大匹配值加上T和T的匹配值,AGT和GT的最大匹配值加上T和-的匹配值,AG和GTT的最大匹配值加上-和T的匹配值中的最大值,所以状态转移方程:
opt[i][j]=max(opt[i-1][j-1]+table(b[i-1],a[j-1]),opt[i][j-1]+table('-',a[j-1]),opt[i-1][j]+table('-',b[i-1]));
Null
A
G
T
G
A
T
G
Null
-3
-5
-6
-8
-11
-12
-14
G
-2
T
-3
T
-4
A
-7
G
-9
第0行,第0列表示null和字符串匹配情况,结果是’-’和各个字符的累加:
for(i=1;i<=num1;i++)
opt[0][i]=opt[0][i-1]+table('-',a[i-1]);
for(i=1;i<=num2;i++)
opt[i][0]=opt[i-1][0]+table('-',b[i-1]);
opt[num2][num1]即为所求结果。
带有详细注释的代码可以在
Pkuacm2192Zipper动态规划题目总结(九)
这个题目要求判断2个字符串能否组成1个字符串,例如cat和tree能组成tcraete。
我们定义一个布尔类型的二维数组array,array[i][j]表示str1[i]和str2[j]能否组成str[i+j].i=0或者j=0表示空字符串,所以初始化时,array[0][j]表示str1的前j个字符是否和str都匹配。
对于str=tcraete:
Null
c
a
t
Null
1
0
0
0
t
1
r
0
e
0
e
0
可以证明:
当array[i-1][j](array[i][j]上面一格)和array[i][j-1](array[i][j]左面一格)都为0时,array[i][j]为0.当array[i-1][j](array[i][j]上面一格)为1且左面字母为str[i+j]时或者当array[i][j-1](array[i][j]左面一格)为1且上面字母为str[i+j]时,array[i][j]为1.这就是状态转移方程为。
核心的Java代码:
if(array[i][j-1]&&str1.charAt(j-1)==str.charAt(i+j-1)||array[i-1][j]&&str2.charAt(i-1)==str.charAt(i+j-1))
array[i][j]=true;
else
array[i][j]=false;
带有详细注释的代码可以在
Pkuacm3356AGTC动态规划题目总结(十)
一个字符串可以插入、删除、改变到另一个字符串,求改变的最小步骤。
和最长公共子序列类似,用二维数组opt[i][j]记录字符串a中的前i个字符到字符串b中的前j个字符匹配所需要的最小步数。
假如已知AG到GT的最小步数,AGT到GT的最小步数,AG到GTT的最小步数,求AGT到GTT的最小步数,此时T==T,这个值是AG到GT的最小步数,AGT到GT的最小步数加一(AGT到GT的最小步数等于AGTT到GTT的最小步数,加一是将T删除的一步),AG到GTT的最小步数加一(AG到GTT的最小步数等于AGT到GTTT的最小步数,加一是在AGT上增加T的一步)。
假如已知AG到GT的最小步数,AGA到GT的最小步数,AG到GTT的最小步数,求AGA到GTT的最小步数,此时A!
=T,这个值是AG到GT的最小步数加一(A改变为T),AGA到GT的最小步数加一(AGA到GT的最小步数等于AGAT到GTT的最小步数,加一是将T删除的一步),AG到GTT的最小步数加一(AG到GTT的最小步数等于AGA到GTTA的最小步数,加一是在GTTA上删除A的一步)。
所以状态转移方程:
if(str1.charAt(i-1)==str2.charAt(j-1))
array[i][j]=Math.min(Math.min(array[i-1][j-1],array[i-1][j]+1),array[i][j-1]+1);
else
array[i][j]=Math.min(Math.min(array[i-1][j-1]+1,array[i-1][j]+1),array[i][j-1]+1);
初始化的时候和最长公共子序列不同,因为第0行,第0列表示null转化到字符串情况,结果是字符串的长度:
for(inti=0;i<=m;i++){
array[i][0]=i;
}
for(inti=0;i<=n;i++){
array[0][i]=i;
}
Null
A
G
T
G
A
T
G
Null
0
1
2
3
4
5
6
7
G
1
T
2
T
3
A
4
G
5
结果是array[m][n]
带有详细注释的代码可以在
Pkuacm1887TestingtheCATCHER动态规划题目总结(十一)
题目叙述很繁琐,其实就是求最长下降子序列,这一类题也是动态规划的典型题。
这类问题有两种算法,一种T(o)=O(n^2),另一种T(o)=O(nlogn),这里用第一种,在1631Bridgingsignals的解题报告中介绍第二种。
创建一个一维数组num_array[j],max_array[],num_array[j]表示序列的元素,max_array[i]表示以第i个元素结尾的序列中的最长下降子序列,初始化为1,对于一个max_array[i],遍历前面的每个元素j,如果num_array[j]>num_array[i]且max_array[j]>=max_array[i],那么max_array[j]就要加1,所以递推公式为:
if(num_array[i]<=num_array[j]&&max_array[i]<=max_array[j])
max_array[i]++;
最后选最大的一个max_array[i]就是最长下降子序列的个数。
Java关键部分的代码:
for(inti=1;ifor(intj=0;j
if(num_array[i]<=num_array[j]&&max_array[i]<=max_array[j])
max_array[i]++;
}
max_value=(max_array[i]>max_value)?
max_array[i]:
max_value;
}
max_value是最后的结果。
带有详细注释的代码可以在
Pkuacm2533LongestOrderedSubsequence动态规划题目总结(十二)
这个题目和1887TestingtheCATCHER一模一样,没有什么值得说的,关键的c代码如下:
for(i=1;i<=n;i++)
{
for(j=1;j
if(max[i]<=max[j]&&num[i]>num[j])
max[i]++;
if(max[i]>result)
result=max[i];
}
printf("%d\n",result);
带有详细注释的代码可以在
Pkuacm1631Bridgingsignals动态规划题目总结(十三)
这个题目可以转化为最长上升子序列,这样这个题目似乎就和2533LongestOrderedSubsequence1887TestingtheCATCHER一样了,迅速写下代码,结果超时!
看来只能用O(nlogn)的算法了。
在O(n^2)的算法中:
创建一个一维数组array[j],opt[],array[j]表示序列的元素,opt[i]表示以第i个元素结尾的序列中的最长下降子序列,初始化为1,对于一个opt[i],遍历前面的每个元素j,如果array[j]>array[i]且opt[j]>=opt[i],那么opt[j]就要加1,在这里,遍历前面的每个元素j,寻找此前最大的子序列时间复杂度为O(n),如果我们在一个有序的序列中查找此前最大的序列长度,我们就可以用二分查找,时间复杂度就会降为O(logn),总的时间复杂度就会为O(nlogn)。
为此,我们增加一个一维数组B,B[i]表示当前序列为i的末尾元素的最小值。
例如对于序列:
426315:
i
1
2
3
4
5
6
array
4
2
6
3
1
5
opt
1
1
2
2
1
3
B
1
3
5
构建过程如下:
i=1时,opt[i]=1B[i]=4(当前为1的序列的末尾元素的最小值)
opt
1
1
1
1
1
1
B
4
i=2时,2不大于4,所以opt[i]=1,将B[1]更新为2
opt
1
1
1
1
1
1
B
2
i=3时,6大于2,所以opt[i]=1+1,将B[2]更新为6
opt
1
1
2
1
1
1
B
2
6
i=4时,3在26之间,所以opt[i]=1+1,将B[2]更新为3
opt
1
1
2
2
1
1
B
2
3
i=5时,1小于2,所以opt[i]=1,将B[1]更新为1
opt
1
1
2
2
1
1
B
1
3
i=6时,5大于3,所以opt[i]=2+1,将B[3]更新为5
opt
1
1
2
2
1
3
B
1
3
5
opt[6]就是最后的结果。
从构建的过程可以容易的证明一下两点:
B是递增的。
B是当前序列为i的末尾元素的最小值。
以上“2不大于4”,“3在26之间”等等的判断采用二分查找,所以总的时间复杂度为:
O(nlogn),核心的c代码如下:
for(i=1;i<=n;i++)
{
num=array[i];
left=1;
right=Blen;
while(left<=right)
{
mid=(left+right)/2;
if(B[mid]left=mid+1;
else
right=mid-1;
}
o