【LeetCode】动态规划 编辑距离 字符串 双指针

编辑距离

【参考:代码随想录# 动态规划之编辑距离总结篇# 判断子序列

  • 72.编辑距离
  • 583.两个字符串的删除操作
  • 392.判断子序列

392. 判断子序列 easy

【参考:392. 判断子序列 - 力扣(LeetCode)
【参考:代码随想录# 392.判断子序列

这道题应该算是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。

dp[i][j] 表示以下标 i-1 为结尾的字符串 s,和以下标 j-1 为结尾的字符串 t,相同子序列的长度为 dp[i][j]。

if (s[i - 1] == t[j - 1])
t 中找到了一个字符在 s 中也出现了

if (s[i - 1] != t[j - 1])
相当于 t 要删除元素,继续匹配

if (s[i - 1] == t[j - 1]),那么 dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在 dp[i-1][j-1]的基础上加 1(如果不理解,在回看一下 dp[i][j]的定义)

if (s[i - 1] != t[j - 1]),此时相当于t 要删除元素,t 如果把当前元素 t[j - 1]删除,那么 dp[i][j] 的数值就是 看 s[i - 1]与 t[j - 2]的比较结果 dp[i][j - 1]了,即:dp[i][j] = dp[i][j - 1];

和【1143. 最长公共子序列】类似

class Solution {
    public boolean isSubsequence(String s, String t) {
        int n = s.length(); int m = t.length();
        int[][] dp = new int[n+1][m+1];
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(s.charAt(i-1) == t.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        if(dp[n][m] == n){
            return true;
        }else{
            return false;
        }
    }
}

双指针

【参考:二分查找高效判定子序列 :: labuladong 的算法小抄

class Solution {
    public boolean isSubsequence(String s, String t) {
        int i = 0, j = 0;
        while (i < s.length() && j < t.length()) {
            if (s.charAt(i) == t.charAt(j)) {
                i++;
            }
            j++;
        }
        return i == s.length();
    }
}

72. 编辑距离 hard ***

【参考:72. 编辑距离 - 力扣(LeetCode)

【参考:代码随想录# 72. 编辑距离

编辑距离是用动规来解决的经典题目

d p [ i ] [ j ] dp[i][j] dp[i][j]:以前i个字符(对应的字符串下标为i-1)为结尾的字符串 word1,和以前j个字符(对应的字符串下标为j-1)为结尾的字符串 word2,最近编辑距离为 dp[i][j]

if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?

操作一:word1 删除一个元素,那么就是 word1 的前 i-1 个字符(以下标 i - 2 为结尾的 word1) 与word2 的前 j-1 个字符( j-1 为结尾的 word2)的最近编辑距离 再加上一个操作。
即 dp[i][j] = dp[i - 1][j] + 1;

操作二:word2 删除一个元素,那么就是以下标 i - 1 为结尾的 word1 与 j-2 为结尾的 word2 的最近编辑距离 再加上一个操作。
即 dp[i][j] = dp[i][j - 1] + 1;

这里有同学发现了,怎么都是删除元素,添加元素去哪了。

word1 添加一个元素,相当于 word2 删除一个元素;word2 添加一个元素,相当于 word1 删除一个元素,最终的操作数是一样

操作三:替换元素,word1 替换 word1[i - 1],使其与 word2[j - 1]相同,此时不用增加元素,那么以下标 i-2 为结尾的 word1 与 j-2 为结尾的 word2 的最近编辑距离 加上一个替换元素的操作
即 dp[i][j] = dp[i - 1][j - 1] + 1;

综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,
即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;

初始化:
dp[i][0] :以下标 i-1 为结尾的字符串 word1,和空字符串 word2,最近编辑距离为 dp[i][0]。

那么 dp[i][0]就应该是 i,对 word1 里的元素全部做删除操作,即:dp[i][0] = i;

同理 dp[0][j] = j;

【参考:编辑距离 - 编辑距离 - 力扣(LeetCode)这篇写的更好

本质不同的操作实际上只有三种:

在单词 A 中插入一个字符;

在单词 B 中插入一个字符;

修改单词 A 的一个字符。

class Solution {
    public int minDistance(String word1, String word2) {
        int n = word1.length();
        int m = word2.length();

        int[][] dp = new int[n + 1][m + 1];

        for (int i = 0; i <= n; i++) {
            dp[i][0] = i;
        }
        for (int i = 0; i <= m; i++) {
            dp[0][i] = i;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; 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[n][m];
    }
}

面试题 01.05. 一次编辑 - medium

【参考:面试题 01.05. 一次编辑 - 力扣(LeetCode)

【参考:【宫水三叶】简单双指针模拟 - 一次编辑 - 力扣(LeetCode)

class Solution {
    public boolean oneEditAway(String first, String second) {
        if(first.equals(second)) return true;
        
        int n=first.length(),m=second.length();
        if (Math.abs(n-m)>1) return false;
        
        if (n>m) {
            String temp=first;
            first=second;
            second=temp;
            int x=n;
            n=m;
            m=x;
        }
        // n < m
        char[] c1=first.toCharArray();
        char[] c2=second.toCharArray();

        int i=0,j=0,num=0;
        while(i<n && j<m && num<=1){
            if(c1[i]==c2[j]){
                i++;
                j++;
            }else{
                if (n==m){ // 替换
                    i++;
                    j++;
                    num++;
                }else{  // n<m,i不变,相当于在i位置添加一个字符
                    j++;
                    num++;
                }
            }
        }
       
        return num<=1;
    }
}

dp

class Solution {
    public boolean oneEditAway(String first, String second) {
        if(first.equals(second)) return true;
        
        int n=first.length(),m=second.length();

        int[][] dp = new int[n + 1][m + 1];

        for (int i = 0; i <= n; i++) {
            dp[i][0] = i;
        }
        for (int i = 0; i <= m; i++) {
            dp[0][i] = i;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (first.charAt(i - 1) == second.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[n][m]<=1;
    }
}

583. 两个字符串的删除操作 medium ***

【参考:583. 两个字符串的删除操作 - 力扣(LeetCode)

【参考:代码随想录# 583. 两个字符串的删除操作

方法一:

dp[i][j]:以下标i-1(前i个字符)为结尾的字符串 word1,和以下标j-1(前j个字符)为结尾的字符串 word2,想要达到相等,所需要删除元素的最少次数。

当 word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];

当 word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:

情况一:删 word1[i - 1],最少操作次数为 dp[i - 1][j] + 1

情况二:删 word2[j - 1],最少操作次数为 dp[i][j - 1] + 1

情况三:同时删 word1[i - 1]和 word2[j - 1],操作的最少次数为 dp[i - 1][j - 1] + 2

那最后当然是取最小值,所以当 word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});

dp 数组如何初始化
从递推公式中,可以看出来,dp[i][0] 和 dp[0][j]是一定要初始化的。

dp[i][0]:word2 为空字符串,以 i-1 为结尾的字符串 word1 要删除多少个元素,才能和 word2 相同呢,很明显 dp[i][0] = i。dp[0][j]的话同理。

不要 dp[i - 1][j - 1] + 2 也可以,其实情况一和二已经包括情况三了,但暂时还无法理解

class Solution {
    public int minDistance(String word1, String word2) {
        int n= word1.length(),m=word2.length();
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 0; i < n + 1; i++)
        	dp[i][0] = i;
        for (int j = 0; j < m + 1; j++)
        	dp[0][j] = j;

        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; 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] + 2,
                                        Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                }
            }
        }

        return dp[n][m];
    }
}

方法二:
本题和动态规划:【1143.最长公共子序列】基本相同,只要求出两个字符串的最长公共子序列长度即可,那么除了最长公共子序列之外的字符都是必须删除的,最后用两个字符串的总长度减去两个最长公共子序列的长度就是删除的最少步数。

class Solution {
    public int minDistance(String word1, String word2) {
        int lcs=LCS(word1,word2);
        return word1.length()+word2.length()-lcs-lcs;

    }
    // 计算最长公共子序列的长度
    public int LCS(String word1,String word2){
        int m=word1.length(),n=word2.length();
        char[] cw1=word1.toCharArray();
        char[] cw2=word2.toCharArray();
        // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j]
        int[][] dp=new int[m+1][n+1];
        // base
        // dp[0][...]=0;dp[...][0]=0
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++){
                // 现在 i 和 j 从 1 开始,所以要减一
                if(cw1[i-1]==cw2[j-1]){
                    // s1[i-1] 和 s2[j-1] 必然在 lcs 中
                    dp[i][j]=dp[i-1][j-1]+1;
                }else{
                    // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        return dp[m][n];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值