前一篇学习了动态规划,下面做两道题目
求最长公共子序列(POJ1458)
给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到, 而且每个字符的先后顺序和原串中的先后顺序一致。
Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0
开始思考:输入两个字符串,str1,假设长度为i,str2,假设长度为j 。maxLen(i,j) 表示长度为i的str1与长度为j的str2的最长公共子序列的长度。maxLen(i,j) 足以表示题目的描述,这就是 ‘状态’。
很明显
maxLen(0,n)=0
maxLen(n,0)=0;
其状态转移方程为:(从位置0开始计算)
if(str1[i-1]==str[j-1])
maxLen[i,j]=maxLen[i-1,j-1]+1;
else
maxLen[i,j]=max{maxLen[i-1.j],maxLen[i,j-1]};
那么可以写出代码
注意outputData的二维数组的长度分别为 两个字符串的长度加1。
为什么要加一呢。自己去思考
private void calculate(){
int str1Len=str1.length();
int str2Len=str2.length();
outputData=new int[str1Len+1][str2Len+1];
for(int i=1;i<=str1Len;i++){
for(int j=1;j<=str2Len;j++){
if(str1.charAt(i-1)==str2.charAt(j-1))
outputData[i][j]=outputData[i-1][j-1]+1;
else
outputData[i][j]=Math.max(outputData[i-1][j], outputData[i][j-1]);
}
}
System.out.println("最长公共子序列长度为:"+outputData[str1Len][str2Len]);
}
完整代码如下
package com.luo.dongtaiguihua;
import java.util.Scanner;
import com.luo.util.SortUtils;
public class DongTaiGuiHuaHigh {
String str1;
String str2;
int[][] outputData;
public void run(){
input();
System.out.println("str1,str2=="+str1+","+str2);
calculate();
SortUtils.printArray(outputData);
}
private void input(){
Scanner in=new Scanner(System.in);
String[] strs=in.nextLine().split("\\s+");
str1=strs[0];
str2=strs[1];
}
private void calculate(){
int str1Len=str1.length();
int str2Len=str2.length();
outputData=new int[str1Len+1][str2Len+1];
for(int i=1;i<=str1Len;i++){
for(int j=1;j<=str2Len;j++){
if(str1.charAt(i-1)==str2.charAt(j-1))
outputData[i][j]=outputData[i-1][j-1]+1;
else
outputData[i][j]=Math.max(outputData[i-1][j], outputData[i][j-1]);
}
}
System.out.println("最长公共子序列长度为:"+outputData[str1Len][str2Len]);
}
}
突然豁然开朗了,觉得都没那么难以理解了。
但是动态规划的使用方法是有限制的。首先要可以确切分子问题,其次要满足无后效性。什么叫无后效性,前面说过,其指:当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。
来一道实例:
求最长上升子序列(百练2757)
一个数的序列ai,当a1 < a2 < ... < aS的时候,我们称这个序列是上升的。对于给定的一个序列(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).。
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入数据
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出要求
最长上升子序列的长度。
输入样例
7
1 7 3 5 9 4 8
输出样例
4
开始分析:
1 找出子问题
如何切分子问题呢,求前n个元素的最长上升子序列?如果是这么切分问题是有问题的,为什么呢,因为如果得到一个长度为i最长子序列,而对于i+1 元素,他有可能是大于前面的最长子序列的最大值,也有可能小于前面的最长序列的最大值。即不满足无后效性。
那怎么切分子问题呢。求以ak(k=1.....n) 为终点的最长子序列的长度。只要我们求n次,得到最大值,其值就是原问题的答案。
2 确定状态
子问题只与位置k有关,序列中元素的位置就是子问题的状态,而状态 k 对应的值就是以ak元素作为终点的最长子序列的长度,一共有n中状态。
3 求出状态转移方程
maxLen(k)=max{maxLen(i)}+1; 1<=i<k && ai<ak && k>1
也可以这么写
if(ai<ak)
maxLen(k)=maxLen(i)+1;
那么也可以敲出代码
private void calculate(){
int len=inputData.length;
outputData=new int[len];
for(int i=0;i<len;i++){
outputData[i]=1;
}
// 人人为我型
for(int i=1;i<len;i++){
for(int j=0;j<i;j++){
if(SortUtils.less(inputData[j],inputData[i]))
outputData[i]=outputData[j]+1;//这两句没有什么区别
// outputData[i]=Math.max(outputData[i], outputData[j]+1);
}
}
// 我为人人型
// for(int i=0;i<len;i++){
// for(int j=i+1;j<len;j++){
// if(inputData[i]<inputData[j])
// outputData[j]=outputData[i]+1;
// }
// }
System.out.println("最大上升子序列长度为:"+outputData[len-1]);
SortUtils.printArray(outputData);
}
完整代码如下
package com.luo.dongtaiguihua;
import java.util.Scanner;
import com.luo.util.SortUtils;
public class DongTaiGuiHuaHigher {
int[] inputData;
int[] outputData;
public void run(){
input();
SortUtils.printArray(inputData);
calculate();
}
private void input(){
Scanner in=new Scanner(System.in);
String[] strs=in.nextLine().split(" ");
int len=strs.length;
inputData=new int[len];
for(int i=0;i<len;i++){
inputData[i]=Integer.parseInt(strs[i]);
}
}
private void calculate(){
int len=inputData.length;
outputData=new int[len];
for(int i=0;i<len;i++){
outputData[i]=1;
}
// 人人为我型
for(int i=1;i<len;i++){
for(int j=0;j<i;j++){
if(SortUtils.less(inputData[j],inputData[i]))
outputData[i]=outputData[j]+1;//这两句没有什么区别
// outputData[i]=Math.max(outputData[i], outputData[j]+1);
}
}
// 我为人人型
// for(int i=0;i<len;i++){
// for(int j=i+1;j<len;j++){
// if(inputData[i]<inputData[j])
// outputData[j]=outputData[i]+1;
// }
// }
System.out.println("最大上升子序列长度为:"+outputData[len-1]);
SortUtils.printArray(outputData);
}
}