打开力扣看一道leetcode题目描述,发现力扣中集成了程序面试金典中的题目,一口气刷了4道,直到遇见这道面试题01.05.一次编辑.

暴力求解

我本以为这道题应该也能一遍AC,结果却卡了半个多小时,未能AC的代码如下:

class Solution {
public:
    bool oneEditAway(string first, string second) {
        int m = first.size(),n = second.size();
        //相等返回true
        if(first == second) return true;
        
        //长度相差大于1
        if(abs(m - n) > 1) return false;

        //长度相等,只能有一个不相同
        if(m == n)
        {
            if(m == 1) return true;
            int cnt = 0;
            for(int i = 0;i < m ;i++)
            {   
                if(first[i] != second[i])
                {
                    if(++cnt > 1) return false;
                }
            }
            return true;
        }

        if(abs(m - n ) == 1){
            if(first.find(second) != std::string::npos ||
               second.find(first) !=std::string::npos) {
                return true;   
            }else {
                return false;
            }
        }

        return false;
    }
};

上面的代码可以说是非常直观,问题出在长度差1这种场景,单纯的想到在字符串中查找另一个子串的场景(例如 ab abc,dcba cba),却不能解决(teacher treacher)这种场景(按照目前只能想到暴力求解)。

1145 / 1146 个通过测试用例
状态:解答错误
提交时间:9 小时前
输入:
"teacher"
"treacher"
输出:false
预期:true

代码写到这里其实内心是有点崩溃的,我们分割问题后,长度相差1这种场景依然和原问题一样难以处理。

双指针解法

看到题解列表标题中有人用双指针方法,这里有个我认为的刷题小tip,当陷入僵局时可以看下题解列表标题,有了思路以后再尝试解决,而不是直接看别人的代码,这样收获会更大一些。

class Solution {
public:
    bool oneEditAway(string first, string second) {
        //相等返回true
        if(first == second) return true;
        
        //长度相差大于1,返回false
        int m = first.size(),n = second.size();
        if(abs(m - n) > 1) return false;
        //长度相差小于等于1
        int l = 0,r1 = m -1,r2 = n - 1;
        while(l <= r1 && l <= r2 && first[l] == second[l]) l++;
        while(r1 >= 0 && r2 >= 0 && first[r1] == second[r2]) {r1--;r2--;}
        if(r1-l < 1 && r2-l < 1) return true;
        
        return false;
    }
};

运行效率:

执行结果:通过
显示详情
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.1 MB, 在所有 C++ 提交中击败了90.70%的用户

动态规划

dp[i+1][j+1]表示A的前i个字符与B的前j个字符,如果要通过替换 插入 删除变成一致最少需要的步数时多少?
如果A[i]和B[j]相等,那么dp[i+1][j+1]= dp[i][j];(A的前i个字符要与B的前j个字符变换一致,所需要的最小步骤与A的前i-1个字符和B的前j-1个字符变换需要的步数一致)

如果A[i]和B[j]不相等,这里针对A来说存在三种情况(A[i]前插入 删除A[i] 替换A[i])
替换:dp[i+1][j+1] = dp[i][j] + 1;替换A[i]或B[j]后,只需要计算A的前i-1个字符与B的j-1个最小步数。
删除A[i]: dp[i+1][j+1] = dp[i][j+1] + 1;
A[i]后面插入B[j]:dp[i+1][j+1] =dp[i+1][j] + 1;在原来A[i]后面插入B[j],dp[i+1][j+1] = dp[i+1][j] + 1.
这里需要解释下,由于已经在A[i]后面插入了B[j],那么此时A[i+1] == B[j],dp[i+1][j+1] = dp[i+1][j] + 1,这里只要计算A中前i个字符与B中前j-1个字符变成一致需要最少的操作步骤。

class Solution {
public:
    bool oneEditAway(string first, string second) { 
        int m = first.size(),n = second.size();
        int dp[m+1][n+1];
        dp[0][0] = 0;

        //其中一个字符串为空时,更新最小操作次数
        for(int i = 1;i <= m;i++) dp[i][0] = i;
        for(int i = 1;i <= n;i++) dp[0][i] = i;

        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(first[i] == second[j]) 
                    dp[i+1][j+1] = dp[i][j];
                else 
                    dp[i+1][j+1] = min(min(dp[i][j],dp[i+1][j]),dp[i][j+1]) + 1;
            }
        }
        if(dp[m][n] <= 1) return true;
        return false;
    }
};