处理两个字符串的动态规划问题
一般来说,处理字符串的动态规划问题,可以借鉴参考下面这张图:
第一步,一定要明确 dp 数组的含义。对于两个字符串的动态规划问题,套路是通用的。比如说对于字符串 s1 和 s2,一般来说都要构造一个这样的 DP table:
LeetCode72 编辑距离
编辑距离>>>
参考题解>>>
解决两个字符串的动态规划问题,一般都是用两个指针i,j分别指向两个字符串的最后,然后一步步往前走,缩小问题的规模
package com.zj.FDynamicProgramming.taolu;
/**
* @Author Zhou Jian
* @Date 2020/8/9
* 编辑距离
*/
public class Problem72 {
/**
* @param word1
* @param word2
* @return
* s1--->s2
* s2走完,s1没走完将s1缩短为s2
* if s1[i] == s2[j]:
* 啥都别做(skip)
* i, j 同时向前移动
* else:
* 三选一:
* 插入(insert)
* 删除(delete)
* 替换(replace)
*
* 有这个框架,问题就已经解决了。读者也许会问,这个「三选一」到底该怎么选择呢?
* 很简单,全试一遍,哪个操作最后得到的编辑距离最小,就选谁。这里需要递归技巧,理解需要点技巧,先看下代
* dp[i][j] s1[0,,,,,i] 到s2[0.....j]的最小编辑距离
* dp[0][j] = j
* dp[i][0] = i
* dp[i][j] = min(dp[i-1][j]+1,dp[i-1][j-1]+1 ,dp[i][j-1]+1)
* 有了之前递归解法的铺垫,应该很容易理解。dp[..][0] 和 dp[0][..] 对应 base case,dp[i][j] 的含义和之前的 dp 函数类似:
*/
public int minDistance(String word1, String word2) {
if(word1==null&&word2==null) return 0;
if(word1.length()==0&&word2.length()==0) return 0;
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int j=0;j<=word1.length();j++) dp[j][0] = j;
for(int i=0;i<=word2.length();i++) dp[0][i]= i;
for(int i=1;i<=word1.length();i++){
for(int j=1;j<=word2.length();j++){
// 相等则不需要操作
if(word1.charAt(i)==word2.charAt(j)) {
dp[i][j] = dp[i-1][j-1];
continue;
}
// 若不等则从增加,删除,修改中选择一个最小的
dp[i][j] = Math.min(dp[i-1][j]+1,dp[i][j-1]+1);
dp[i][j] = Math.min(dp[i][j],dp[i-1][j-1]+1);
}
}
return dp[word1.length()][word2.length()];
}
}
LeetCode1143 两个字符串最长公共子序列
package com.zj.FDynamicProgramming.taolu;
/**
* @Author Zhou Jian
* @Date 2020/8/9
*/
public class Problem1143 {
/**
*
* @param text1
* @param text2
* @return
*
* dp[i][j] 两个字符串中 s1[0...i] s2[0.....j]的最长公共子序列
* dp[0][j] = 0 dp[i][0] = 0
*
* if(s1[i]==s2[j]) dp[i][j] = dp[i-1][j-1]+1
* dp[i][j] = max(dp[i-1][j-1]
* dp[i-1][j]
* dp[i][j-1])
*
*最长公共子序列(Longest Common Subsequence,简称 LCS)是一道非常经典的面试题目,因为它的解法是典型的二维动态规划,
*大部分比较困难的字符串问题都和这个问题一个套路,比如说编辑距离。而且,这个算法稍加改造就可以用于解决其他问题,
*所以说 LCS 算法是值得掌握的。
*
* 肯定有读者会问,为啥这个问题就是动态规划来解决呢?因为子序列类型的问题,穷举出所有可能的结果都不容易
* ,而动态规划算法做的就是穷举 + 剪枝,它俩天生一对儿。所以可以说只要涉及子序列问题,十有八九都需要动态规划来解决,往这方面考虑就对了。
*/
public int longestCommonSubsequence(String text1, String text2) {
if(text1==null||text2==null) return 0;
if(text1.length()==0||text2.length()==0) return 0;
int[][] dp = new int[text1.length()+1][text2.length()+1];
for(int i=0;i<text1.length()+1;i++) dp[i][0] = 0;
for(int j=0;j<text2.length()+1;j++) dp[0][j] = 0;
for(int i=1;i<dp.length;i++){
for(int j=1;j<dp[0].length;j++){
// 若两个字符相等则从 dp[i][j] text1中前i个字符 与 text2中前j个字符的最长公共子序列
if(text1.charAt(i-1)==text2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
continue;
}
// 字符串s1[0..i] s2[0j]最长公共子序列的长度
// s1删除一个 或者 s2删除一个 或者两个都删除
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-1]);
}
}
return dp[text1.length()][text2.length()];
}
}