今天这三道题属于动态规划。
题目一:最长公共子序列(二)
注意:子序列不是子串,子串要求所有字符必须连续,子序列不要求连续,只要求相对位置不变。
题目描述:
给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列
输入输出描述:
输入:"1A2C3D4B56","B1D23A456A" 返回值:"123456"
输入:"abc","def" 返回值:"-1"
题目解析:
动态规划+递归
- step 1:优先检查特殊情况。
- step 2:获取最长公共子序列的长度可以使用动态规划,我们以dp[i][j]表示在s1中以i结尾,s2中以j结尾的字符串的最长公共子序列。
- step 3:遍历两个字符串的所有位置,开始状态转移:若是i位与j位的字符相等,则该问题可以变成s1.charAt(i-1)+dp[i−1][j−1].
- step 4:若是不相等,说明到此处为止的子串,最后一位不可能同时属于最长公共子序列,毕竟它们都不相同,因此我们考虑换成两个子问题,dp[i][j−1]或者dp[i−1][j],我们取较大的一个就可以了,由此感觉可以用递归解决。
作答情况:
①注意拼接顺序:如果相同,相同的字符应放在尾,继续往前递归,下面写法是错误的;
②比较内容是否相同时,使用equals最好,最初使用==发生了错误,下面写法是错误的。
代码:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* longest common subsequence
* @param s1 string字符串 the string
* @param s2 string字符串 the string
* @return string字符串
*/
public String LCS (String s1, String s2) {
int m=s1.length();int n=s2.length();
//s1,s2第一个字符对应的下标是1
String[][] dp=new String[m+1][n+1];
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
//其中有一个是空字符串
if(i==0||j==0) dp[i][j]="";
else if(s1.charAt(i-1)==s2.charAt(j-1)) dp[i][j]=dp[i-1][j-1]+s1.charAt(i-1);
else dp[i][j]=dp[i-1][j].length()>dp[i][j-1].length()?dp[i-1][j]:dp[i][j-1];
}
}
return dp[m][n].equals("") ? "-1":dp[m][n];
}
}
题目二:最长公共子串
注意:子串要求所有字符必须连续
题目描述:
给定两个字符串str1和str2,输出两个字符串的最长公共子串
题目保证str1和str2的最长公共子串存在且唯一。
输入输出描述:
输入:"1AB2345CD","12345EF" 返回值:"2345"
题目解析:
动态规划+递归
- step 1:优先检查特殊情况。
- step 2:获取最长公共子序列的长度可以使用动态规划,我们以dp[i][j]表示字符串str1中第i个字符和str2种第j个字符为最后一个元素所构成的最长公共子串.
- step 3:遍历两个字符串的所有位置,开始状态转移:若是i位与j位的字符相等,则该问题可以变成s1.charAt(i-1)+dp[i−1][j−1].
- step 4:如果不相等,那么他们就不能构成公共子串.
作答情况:
①一个字符串里可能有很多子串,所以要进行子串的比较,所以定义一个结果集把前一个字串保存起来方便与后一个字串长度进行比较选出最长子串。这一点我忽略掉了,直接返回dp[m][n]是错误的。
代码:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
public String LCS (String str1, String str2) {
int m=str1.length();int n=str2.length();
//结果
String res="";
//假设字符串中第一个字符最小下标为1
String[][] dp=new String[m+1][n+1];
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
//任意一个字符串为空
if(i==0||j==0) dp[i][j]="";
//各自末尾处字符相同,往前递归
else if(str1.charAt(i-1)==str2.charAt(j-1)) dp[i][j]=dp[i-1][j-1]+str1.charAt(i-1);
//各自末尾处字符不同,不连续了,也不用递归
else dp[i][j]="";
res=(res.length()>dp[i][j].length())?res:dp[i][j];
}
}
return res;
}
}
题目三:最长上升子序列(一)
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。
题目描述:
给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
输入输出描述:
输入:[6,3,1,5,2,3,7] 返回值:4
说明:该数组最长上升子序列为 [1,2,3,7] ,长度为4
题目解析:
- step 1:用dp[i]表示到元素i结尾时,最长的子序列的长度,初始化为1,因为只有数组有元素,至少有一个算是递增。
- step 2:第一层遍历数组每个位置,得到n个长度的子数组。
- step 3:第二层遍历相应子数组求对应到元素i结尾时的最长递增序列长度,期间维护最大值。
- step 4:对于每一个到i结尾的子数组,如果遍历过程中遇到元素j小于结尾元素,说明以该元素结尾的子序列加上子数组末尾元素也是严格递增的,因此转移方程为dp[i]=dp[j]+1。
作答情况:
这一步没有进行取最大,直接在if语句里写dp[i]=dp[j]+1
代码:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 给定数组的最长严格上升子序列的长度。
* @param arr int整型一维数组 给定的数组
* @return int整型
*/
public int LIS (int[] arr) {
int m=arr.length;
if(m==0) return 0;
int result=1;
//dp[i] 为以i位置结尾最长上升序列
int[] dp=new int[m];
//从任意位置开始,未开始比较之前,它相对于自己最长上升字串为1,所以结果可先全部初始化为1
Arrays.fill(dp,1);
for(int i=0;i<m;i++){
//j永远在i前面
for(int j=0;j<i;j++){
if(arr[j]<arr[i]){
//i位置之前最长上升子序列有很多,要进行比较
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
//结果集中最长上升子序列有很多,要进行比较
result=Math.max(result,dp[i]);
}
return result;
}
}