力扣爆刷第81天–动态规划一网打尽字符串删除和回文子串
一、583. 两个字符串的删除操作
题目链接:https://leetcode.cn/problems/delete-operation-for-two-strings/description/
思路:求两个字符串经过最少的删除操作,使得两个字符串相等,初始化要格外注意,定义dp[i][j]表示区间word1[0,i]和区间word2[0,j]相等所需要的最少的删除操作,那么如果word1[i]==word2[j]说明不需要删除,直接延续上一个位置,即dp[i][j]=dp[i-1][j-1],如果word1[i] != word2[j],就需要进行删除操作了,但是需要考虑删除word1还是word2,这取决于删除那边所需要的删除操作最少,如"abc"和"abe",c与e不相等,需要从"abc"和"ab"或者"ab"和"abe"中进行选择,即 dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + 1;
初始化也是一个需要考虑的点,当word1="“时,word2=“abc”,那么第一列需要初始化为[0, 1, 2, 3],表示需要进行的删除操作;当word2=”"时,word1=“abc”,那么第一行需要初始化为[0, 1, 2, 3],表示需要进行的删除操作。
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i = 0; i <= word1.length(); i++) {
dp[i][0] = i;
}
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-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
}else {
dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + 1;
}
}
}
return dp[word1.length()][word2.length()];
}
}
二、72. 编辑距离
题目链接:https://leetcode.cn/problems/edit-distance/
思路:上一题是只有删除操作,本题确多了插入和替换操作,其实删除和插入是一个操作,如abc与ab可以把c删掉,也可以给ab加上c,这是一个等价操作,但是替换不太一样,如ab和ac,替换操作相比删除操作少了一步,b与c不同,替换的话只需要从dp[i-1][j-1]+1即可。
依然是定义dp[i][j]表示区间word1[0, i]和word2[0, j]相等所需要的最少操作,如果word1[i]==word2[i]那么,当前位置不需要进行操作,延续上一个位置,dp[i][j] = dp[i-1][j-1];如果word1[i] != word2[i],那么需要考虑替换还是删除(插入和删除一个意思),dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1;
初始化和上一题一样,另外就是一定要手动推导dp数组,来看看是否匹配递推公式,或者没想出来递推公式,要根据定义从手动推导的dp数组的结果中归纳,下面以一个例子举例:
输入:word1 = “horse”, word2 = “ros”
输出:3
dp数组推导过程:
0 1 2 3
1 1 2 3
2 2 1 2
3 2 2 3
4 3 3 2
5 4 4 3
举一个替换的例子,dp[1][1]时,h和r不等,可以考虑替换,从dp[0][0]+1可达到。
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i = 0; i <= word1.length(); i++) {
dp[i][0] = i;
}
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-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
}else {
dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1;
}
}
}
return dp[word1.length()][word2.length()];
}
}
三、647. 回文子串
题目链接:https://leetcode.cn/problems/palindromic-substrings/
思路:求回文子串的数量,可以使用动态规划或者双指针,先介绍动态规划,如s=abcba,其中s[0]=s[4],如果我们要是知道bcb是回文,那么就可以知道s是回文,这个过程就是一个推导的过程,由此我们可以发现动态规划的dp复用与推导,另外就是如s[1]=s[3],且相距小于等于2,我们也可以推导出是回文,那么定义一个dp[i][j]表示,区间s[i, j]内的字符串是回文子串,且0<=i<len,0<=j<len,那么上面的推导是s[i]==s[j],需要dp[i+1][j-1],也就是行要向下一行,列要向左一行,也就是当前dp[i][j]需要来自左下角的信息,这个决定了遍历顺序,需要从下往上,从左往右,具体如下代码。
class Solution {
public int countSubstrings(String s) {
int len = s.length();
boolean[][] dp = new boolean[len][len];
int sum = 0;
for(int i = len-1; i >= 0; i--) {
for(int j = i; j < len; j++) {
if(s.charAt(i) == s.charAt(j) && (j-i<=2 || dp[i+1][j-1])) {
dp[i][j] = true;
sum++;
}
}
}
return sum;
}
}
双指针法:有两种类型的回文子串aba和abba,然后遍历计算即可。
class Solution {
public int countSubstrings(String s) {
int sum = 0;
for(int i = 0; i < s.length(); i++) {
sum += getCount(s, i, i);
sum += getCount(s, i, i+1);
}
return sum;
}
int getCount(String s, int i, int j) {
int sum = 0;
while(i >= 0 && j < s.length()) {
if(s.charAt(i) == s.charAt(j)) {
sum++;
i--;
j++;
}else {
break;
}
}
return sum;
}
}
四、516.最长回文子序列
题目链接:https://leetcode.cn/problems/longest-palindromic-subsequence/description/
思路:和上一题回溯类似,状态由左下角推出,相等时 dp[i][j] = dp[i+1][j-1] + 2,不等时dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
输入:s = “bbbab”
输出:4
解释:一个可能的最长回文子序列为 “bbbb” 。
1 2 3 3 4
0 1 2 2 3
0 0 1 1 2
0 0 0 1 1
0 0 0 0 1
class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int[][] dp = new int[len][len];
for (int i = 0; i < len; i++) {
dp[i][i] = 1;
}
for (int i = len-1; i >= 0; i--) {
for (int j = i+1; j < len; j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i+1][j-1] + 2;
}else {
dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][len-1];
}
}