经典的DP题,面试的时候直接用DP写就好了,不用从暴力递归,到数据记录,再到枚举糙动态规划,
DP 是递归时候 发现的用空间换时间的方法, 比如LCS:要求左上,左边,上边,再做决策,然后这三者都是有重复计算的,所以考虑用DP把结果存下来,下次重复利用
用DP的时候,先明确一点:dp[i][j]表示的是什么!然后想怎么根据之前的计算决策当前!(决策的方案可以先从最简单的情况入手(比如输入只有1行。。。),推导出规律,然后看能不能应用到一般情况)
1. 最长递增子序列
对于一个数字序列,请设计一个复杂度为O(nlogn)的算法,返回该序列的最长上升子序列的长度,这里的子序列定义为这样一个序列U1,U2...,其中Ui < Ui+1,且A[Ui] < A[Ui+1]。
给定一个数字序列A及序列的长度n,请返回最长上升子序列的长度。
[2,1,4,3,1,5,6],7
返回:4
import java.util.*;
/*
* 递归是可以解得
* 那就可以考虑DP来优化
*/
public class AscentSequence {
public int findLongest(int[] A, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
int rst = 1;
for(int i=1; i<n; i++)
for(int j=0; j<i; j++)
if(A[i] > A[j])
dp[i] = Math.max(dp[i], dp[j]+1);
for(int i : dp) rst = Math.max(rst, i);
return rst;
}
}
2.最长公共子序列
对于两个字符串,请设计一个高效算法,求他们的最长公共子序列的长度,这里的最长公共子序列定义为有两个序列U1,U2,U3...Un和V1,V2,V3...Vn,其中Ui<Ui+1,Vi<Vi+1。且A[Ui] == B[Vi]。
给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。
"1A2C3D4B56",10,"B1D23CA45B6A",12
返回:6
public class LCS {
public int findLCS(String A, int n, String B, int m) {
char[] s1 = A.toCharArray(), s2 = B.toCharArray();
int[][] dp = new int[s1.length+1][s2.length+1];
for(int i=1; i<=s1.length; i++) {
for(int j=1; j<=s2.length; j++) {
if(s1[i-1] == s2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
return dp[s1.length][s2.length];
}
}
如果还要求路径,从右下角方向推回去就行了:如果dp[i][j]比dp[i-1][j]和dp[i][j-1]都大,那说明dp[i][j]是由dp[i-1][j-1]推过来的,所以i,j位置对应的字符一定就出现在LCS中,就要往左上走,反之就可以往左走或者往上走
3. 最长公共子串
对于两个字符串,请设计一个时间复杂度为O(m*n)的算法(这里的m和n为两串的长度),求出两串的最长公共子串的长度。这里的最长公共子串的定义为两个序列U1,U2,..Un和V1,V2,...Vn,其中Ui + 1 == Ui+1,Vi + 1 == Vi+1,同时Ui == Vi。
给定两个字符串A和B,同时给定两串的长度n和m。
"1AB2345CD",9,"12345EF",7
返回:4
public class LongestSubstring {
public int findLongest(String A, int n, String B, int m) {
char[] s1 = A.toCharArray(), s2 = B.toCharArray();
int[][] dp = new int[s1.length+1][s2.length+1];
int rst = 0;
for(int i=1; i<=s1.length; i++) {
for(int j=1; j<=s2.length; j++) {
if(s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
rst = Math.max(rst, dp[i][j]);
}
}
}
return rst;
}
}
用while循环模拟对应关系,用一个变量对应而不是构建矩阵:可以做到O(1)复杂度
4. 最小编辑代价
对于两个字符串A和B,我们需要进行插入、删除和修改操作将A串变为B串,定义c0,c1,c2分别为三种操作的代价,请设计一个高效算法,求出将A串变为B串所需要的最少代价。
给定两个字符串A和B,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。保证两串长度均小于等于300,且三种代价值均小于等于100。
"abc",3,"adc",3,5,3,100
返回:8
/*
* 直接DP,不要先算步数
* 但也是对dp[i][j]有三种决策方案
*/
public class MinCost {
public int findMinCost(String A, int n, String B, int m, int c0, int c1, int c2) {
char[] s1 = A.toCharArray(), s2 = B.toCharArray();
int[][] dp = new int[s1.length+1][s2.length+1];
// 这里需要注意初始化的问题,dp[0][i]对应的不是0
for(int i=0; i<s2.length+1; i++)
dp[0][i] = i * c0;
for(int i=0; i<s1.length+1; i++)
dp[i][0] = i * c1;
for(int i=1; i<=s1.length; i++) {
for(int j=1; j<=s2.length; j++) {
if(s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1];
} else {
// 用A的最后一位update为B的最后一位
int t1 = dp[i-1][j-1] + c2;
// 在A最后面插入一位成为B的最后一位
int t2 = dp[i][j-1] + c0;
// 在A最后面删除一位,露出来的最后一位成为B的最后一位
int t3 = dp[i-1][j] + c1;
dp[i][j] = Math.min(t1, Math.min(t2, t3));
}
}
}
return dp[s1.length][s2.length];
}
}
这个题还可以用滚动数组优化空间
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
/*
* 这些题都是可以写出递归解法的
* 接下来可以考虑怎么把递归的解法写成DP的方式
*/
public class Mixture {
public boolean chkMixture(String A, int n, String B, int m, String C, int v) {
if(n + m != v) return false;
char[] s1 = A.toCharArray(), s2 = B.toCharArray(), s3 = C.toCharArray();
return check(s1, 0, s2, 0, s3, 0);
}
private boolean check(char[] s1, int i, char[] s2, int j, char[] s3, int k) {
if(k == s3.length) return true;
if(i<s1.length && s1[i] == s3[k] && check(s1, i+1, s2, j, s3, k+1)) return true;
if(j<s2.length && s2[j] == s3[k] && check(s1, i, s2, j+1, s3, k+1)) return true;
return false;
}
public boolean chkMixture(String A, int n, String B, int m, String C, int v) {
if(n + m != v) return false;
char[] s1 = A.toCharArray(), s2 = B.toCharArray(), s3 = C.toCharArray();
boolean[][] dp = new boolean[n+1][m+1];
dp[0][0] = true;
for(int i=1; i<=v; i++)
for(int j=0; j<=i-1 && j<=n; j++)
if(i-1-j<=m && dp[j][i-1-j]) {
if(j<n && s1[j]==s3[i-1])
dp[j+1][i-1-j] = true;
// 与上面不是if-else的关系,两者可能都是可以的
if(i-1-j<m && s2[i-1-j]==s3[i-1])
dp[j][i-j] = true;
}
return dp[n][m];
}
}