比较含退格的字符串

问题描述

给定 ST 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。

注意:如果对空文本输入退格字符,文本继续为空。

 

示例 1:

输入:S = "ab#c", T = "ad#c" 输出:true 解释:S 和 T 都会变成 “ac”。

示例 2:

输入:S = "ab##", T = "c#d#" 输出:true 解释:S 和 T 都会变成 “”。

示例 3:

输入:S = "a##c", T = "#a#c" 输出:true 解释:S 和 T 都会变成 “c”。

示例 4:

输入:S = "a#c", T = "b" 输出:false 解释:S 会变成 “c”,但 T 仍然是 “b”。

 

提示:

  1. 1 <= S.length <= 200
  2. 1 <= T.length <= 200
  3. ST 只含有小写字母以及字符 '#'

 

进阶:

  • 你可以用 O(N) 的时间复杂度和 O(1) 的空间复杂度解决该问题吗?

解题思路

方法一:直接比较法

最简单的方法就是直接将两字符串中的#和其前一个字符去掉,比较剩余的字符是否相等。若相等则返回true

我们如何在最好的时间内去掉#和其前一个字符呢?

在不考虑空间开销的情况下,我们可以考虑构造两个辅助栈来完成操作。如果遇到#,那么就取出栈顶的元素,这样也就模拟了删掉#前一个字符的效果。那么最后栈中剩余的元素就是去掉退格字符后的所有剩余字符。然后就可以将两个栈中的字符依次比较是否相同。这种方法的代码如下:

class Solution {
    public boolean backspaceCompare(String S, String T) {
        Stack<Character> stack1 = new Stack<>();
        Stack<Character> stack2 = new Stack<>();
        for(int i=0; i<S.length(); i++){
            if(S.charAt(i)!='#') stack1.add(S.charAt(i));
            else if(!stack1.isEmpty()) stack1.pop();
        }
        for(int j=0; j<T.length(); j++){
            if(T.charAt(j)!='#') stack2.add(T.charAt(j));
            else if(!stack2.isEmpty()) stack2.pop();
        }
        return stack1.equals(stack2);
    }
}
110 / 110 个通过测试用例
状态:通过
执行用时: 3 ms
内存消耗: 36.3 MB

由于额外创建了两个辅助栈,所以时间复杂度还是有一些偏高。我们再尝试做一些改进:

那么本题,确实可以使用栈的思路,但是没有必要使用栈,因为最后比较的时候还要比较栈里的元素,有点麻烦。为了减轻开销,可以使用拼接字符串的方式将剩余的字符进行拼接,最后进行比较的是便是两个字符串。这样就会比比较两个集合的元素是否相同还要更快。

我们使用Java中的StringBuffer类来实现字符串的拼接。如果没有遇到#字符,则将该字符拼接到StringBuffer的对象中,否则删除StringBuffer对象的最后一个字符。最后返回两者对象的字符串进行比较。代码实现如下:

class Main {

    public static void backspaceCompare(String S, String T) {
        build(S).equals(build(T));
    }

    public static String build(String str) {
        StringBuffer sb = new StringBuffer();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            if (str.charAt(i) != '#')
                sb.append(str.charAt(i));
            else if (sb.length() > 0)
                sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }
}
110 / 110 个通过测试用例
状态:通过
执行用时: 1 ms
内存消耗: 36.4 MB

两次的结果进行对比可以看到,从时间复杂度上,改进之后的方法执行时间更短。但是两次方法的内存消耗没有什么大的变化。有没有一种方法,既可以保持这种优秀的执行时间还保证内存消耗的更少呢?下面就介绍这种双指针的方法来减少内存消耗。

方法二:双指针法:

同时从后向前遍历S和T(i初始为S末尾,j初始为T末尾),记录#的数量,模拟消除的操作,如果#用完了,就开始比较S[i]S[j]

具体地,我们定义 skip 表示当前待删除的字符的数量。每次我们遍历到一个字符:

  • 若该字符为退格符,则我们需要多删除一个普通字符,我们让 skip1

  • 若该字符为普通字符:

    • skip0,则说明当前字符不需要删去;

    • skip不为 0,则说明当前字符需要删去,我们让skip1

比较含退格的字符串

如果S[i]S[j]不相同返回false,如果有一个指针(i或者j)先走到的字符串头部位置,也返回false

代码实现

class Solution {
    public boolean backspaceCompare(String S, String T) {
        int skipS = 0, skipT = 0;
        int i = S.length()-1, j = T.length()-1;
        while(true){
            while(i>=0){
                if(S.charAt(i) == '#') skipS++;
                else{
                    if(skipS>0) skipS--;
                    else break;
                }
                i--;
            }
            while(j>=0){
                if(T.charAt(j) == '#') skipT++;
                else{
                    if(skipT>0) skipT--;
                    else break;
                }
                j--;
            }

            if(i<0 || j<0) break;
            if(S.charAt(i)!=T.charAt(j)) return false;
            i--;j--;
        }
        if(i == -1 && j == -1) return true;
        return false;
    }
}

复杂度分析

时间复杂度:O(N+M),其中 NM 分别为字符串 ST 的长度。我们需要遍历两字符串各一次。

空间复杂度:O(1)。对于每个字符串,我们只需要定义一个指针和一个计数器即可。

110 / 110 个通过测试用例
状态:通过
执行用时: 0 ms
内存消耗: 36.4 MB