【C/C++】面试题 01.05. 一次编辑

121 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情


题目链接:面试题 01.05. 一次编辑

题目描述

字符串有三种编辑操作:插入一个英文字符、删除一个英文字符或者替换一个英文字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。

示例 1:

输入: 
first = "pale"
second = "ple"
输出: True

示例 2:

输入: 
first = "pales"
second = "pal"
输出: False

整理题意

给定两个字符串,问是否能够通过一次操作使得两个字符串相同,操作有三种,分别是插入、删除和替换。如果可以返回 true,否则返回 false

解题思路分析

方法一:分类讨论

该题要求只能操作一次,那么可能的情况并不多,我们可以分类讨论:

  1. 首先判断两个字符串长度:
    • 长度相等,继续判断是否可以通过替换变为相同字符串
    • 长度不相等,判断长度差值是否小于等于 1 ,如果字符串长度差值在 1 以内,我们可以对字符串进行插入或删除操作;如果大于 1 那么无法通过一次编辑使得两个字符串相等。
  2. 其次遍历两个字符串,当 首次 遇到不相同字符时:
    • 如果俩字符串长度相等,跳过当前位置的字符,因为我们可以通过替换改为相同;
    • 如果俩字符串长度差值在 1 以内,将较长的字符串跳过当前字符位置,而较短的字符串继续匹配当前位置。(这里可以理解为给较短字符串插入一个字符,也可以理解为将较长字符串的当前字符删除)
  3. 如果再次遇到不相同字符时,那么我们将无法通过一次编辑使得两个字符串相等,此时返回 false;
  4. 最后如果字符串仅有一处字符不相同,且长度差值在 1 以内,那么此时我们可以通过一次编辑使得俩字符串相同,此时返回 true

方法二:前后相同字符串个数

还可以换个角度思考该题,既然我们只能编辑一次,也就是两个字符串只能有且仅有一处不相同。在长度差值在 1 以内时可能是需要插入或者删除,而当长度相同时只可能是替换。

那么我们分别从开头和末尾进行两次遍历,统计俩字符串从开头开始连续相同的字符个数和从末尾开始连续相同的字符个数。也就是判断俩字符串最长的连续相同的前缀和后缀 ,分别考虑当俩字符串长度相等时和长度差值等于 1 时可以通过一次编辑使得两个字符串相等的临界情况:

  • 当俩字符串长度相等时: 一次编辑 (2).jpg
  • 当俩字符串长度差值等于 1 时: 一次编辑 (1).jpg

当前后缀相同个数之和小于临界情况时,我们无法通过一次编辑使得两个字符串相等。而当前后缀相同个数之和大于等于 S1.length - 1 时(S1S_1 为俩字符串中较长的那个字符串),我们可以通过一次编辑使得两个字符串相等。

根据上述分析,此时只需考虑前后缀相同个数之和与较长字符串 S1S_1 的长度之差即可,因为我们可以编辑一次,所以此时俩字符串前后缀之和大于等于较长字符串 S1S_1 的长度 -1 即可。

具体实现

方法一:分类讨论

  1. 首先判断俩字符串长度差值是否在 1 以内。
  2. 声明变量 used 表示是否编辑过。
  3. 同时遍历俩字符串,当遇到不相同字符时判断是否编辑过,如果没有编辑过,此时分类讨论两种情况:
    • 当俩字符串长度相同时,同时跳过当前字符串,表示采用替换操作,标记 used 为编辑过,继续匹配。
    • 当俩字符串长度不相同时,跳过较长字符串的当前字符串,表示采用删除或插入操作,标记 used 为编辑过,继续匹配。
  4. 当遇到不相同字符时且 used 以及被标记为编辑过时,直接返回 false
  5. 当前字符相同时继续匹配,如果遍历结束后没有中途返回 false 的话表示可以通过一次编辑使得两个字符串相等,此时返回 true

方法二:前后相同字符串个数

  1. 从正向遍历一次,统计连续相同前缀的个数;
  2. 从反向遍历一次,统计连续相同后缀的个数;
  3. 判断前后缀相同个数之和与较长字符串 S1S_1 的长度之差是否小于等于 1 即可;

复杂度分析

两种方法复杂度相同:

  • 时间复杂度:O(m+n)O(m + n),其中 mn 分别是字符串 S1S_1S2S_2 的长度。当 mn1|m - n| \le 1 时,需要遍历两个字符串各一次。
  • 空间复杂度:O(1)O(1),仅需常数存储空间。

代码实现

方法一:分类讨论

class Solution {
public:
    bool oneEditAway(string first, string second) {
        int n = first.length();
        int m = second.length();
        //长度差值超过1的情况无法修改为相同
        if(abs(n - m) > 1) return false;
        //used表示是否编辑过
        bool used = false;
        int i = 0, j = 0;
        while(i < n && j < m){
            if(first[i] == second[j]) i++, j++;
            //如果编辑过返回false
            else if(used) return false;
            else{
                //删除较长的一个字符
                if(n > m) i++;
                else if(n < m) j++;
                //n == m 同长修改
                else i++, j++;
                //编辑过
                used = true;
            }
        }
        return true;
    }
};

方法二:前后相同字符串个数

class Solution {
public:
    bool oneEditAway(string first, string second) {
        int n = first.length();
        int m = second.length();
        //长度差值超过1的情况无法修改为相同
        if(abs(n - m) > 1) return false;
        //l表示左边相同个数,r表示右边相同个数
        int l = 0, r = 0;
        for(l = 0; l < n && l < m; l++) if(first[l] != second[l]) break;
        for(r = 0; r < n && r < m; r++) if(first[n - 1 - r] != second[m - 1 - r]) break;
        //左右相同个数相加是否大于 较长字符串 - 1,表示可以操作一次后是否相等
        int num = max(n, m) - l - r;
        if(num > 1) return false;
        else return true;
    }
};

总结

  • 因为该题有且仅有一次编辑操作,情况较少适合分类讨论,很容易想到方法一的做法;但相对于方法二的统计前后缀相同字符个数来说,方法二的思维更好,且代码实现起来非常简练;
  • 方法二的 核心思想在于既然只能编辑一次,那么相同字符的个数应该大于等于较长字符串长度减一
  • 需要注意的是方法一需要提前判断俩字符串长度差值在 1 以内。
  • 在时间和空间复杂度上二者差异并不大: 微信截图_20220525152804.png

结束语

世上没有白费的努力,也没有碰巧的成功,一切无心插柳,其实都是水到渠成。人生没有白走的路,也没有白吃的苦。踏踏实实地做好每件小事,勤勤恳恳地付出努力,你的梦想终将实现。