LeetCode第 97 题:交错字符转(C++)

97. 交错字符串 - 力扣(LeetCode)

文章目录

困难题,第一个难点就是“交错”这个词的意思,想象两把梳子面向对方插过去,比较难解释:

假设 s3 中来自 s1 的字符集合为 A ,来自 s2 的字符集合为 B ,那么A 和 B中元素的相对顺序与 s1或者 s2中是一致的。

看到一个图(类似路径问题,找准状态方程快速求解 ),解释这个题目再形象不过了:
在这里插入图片描述

首先符合要求的 s3 显然有:

  • s3.size() = s1.size() + s2.size()
  • 如果s1/s2为空,s3 = s1 or s2;

回溯

一开始是想到回溯法的思路,不过枚举可能复杂度太高了。

假设 n = s3.size(),我们每次从s1 or s2中取1个字符,一共需要取n次,当碰到岔路口的时候,比如s1中的下一个待取元素与s2中的下一个待取元素相同,那就有两种选择。

三指针思路:i, j, k分别指向s3, s1, s2的下一个待考虑元素。

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        if(s1.empty())  return s2 == s3;
        if(s2.empty())  return s1 == s3;

        int j = 0, k = 0; //用于记录s1,s2中的下一个待选元素下标
        for(int i = 0; i < s3.size(); ++i){
            if(s1[j] != s2[k]){//只有一种选择
                  //选择s1
                if(s3[i] == s1[j])  ++j;
                //选择s2
                else if(s3[i] == s2[k]) ++k;
                // 选哪个都不行
                else return false;
            }else{//有两种选择
                //如果两种选择都不满足要求
                if(s3[i] != s1[j])   return false;
                //两种选法进入不同路口
                bool flag1 = isInterleave(s1.substr(j+1),s2.substr(k), s3.substr(i+1) ); //选s1
                if(flag1)   return true;
                bool flag2 = isInterleave(s1.substr(j),s2.substr(k+1), s3.substr(i+1)); //选s2
                return flag2;
            }

            // 此时j == s1.size() 或者 k == s2.size(),有一个字符串全部被选完
            if(j == s1.size())  return s2.substr(k) == s3.substr(i+1);
            if(k == s2.size())  return s1.substr(j) == s3.substr(i+1);
        } 
        return true;
    }
};

上述代码逻辑挺简单的,但是第99个用例过不去,超出时间限制,应该是递归的问题,而且存在大量的重复计算,也就是有些路径走了多次。

那尝试给递归加上记忆化吧,走过的路径直接记录它的状态,下次再走的时候直接查询状态即可。上面的代码主要涉及 i, j, k三个参数,但是其实总会有i + j = k,严格来说我们只需要一个二维(i,j)数组就能存储所有的状态了。

代码还是由上面修改而来,顺利通过,效率也很高:

class Solution {
public:
    // p1, p2, p3分别是s1, s2, s3中下一个待选元素下标
    bool interleave(string &s1, int p1, string &s2, int p2, string &s3, int p3, vector<vector<int>> &vec){
        if(vec[p1][p2] != -1)   return vec[p1][p2];//已经存在记录,直接返回相应值
        // 如果p1 == s1.size() 或者 p2 == s2.size(),则有一个字符串被选完,只需比较另一个字符串和s3的剩余部分是否相等即可
        if(p1 == s1.size()){
            vec[p1][p2] = s2.substr(p2) == s3.substr(p3) ? 1 : 0;
            return s2.substr(p2) == s3.substr(p3);
        }  
        if(p2 == s2.size()){
            vec[p1][p2] = s1.substr(p1) == s3.substr(p3) ? 1 : 0;
            return s1.substr(p1) == s3.substr(p3);
        }  
        //for循环其实不是必须的,可以每一个元素都选或者不选,然后进入不同的分支
        for(int i = p3; i < s3.size(); ++i){
            if(s1[p1] != s2[p2]){//只有一种选择
                  //选择s1
                if(s3[i] == s1[p1])  ++p1;
                //选择s2
                else if(s3[i] == s2[p2]) ++p2;
                // 选哪个都不行
                else return false;
            }else{//有两种选择
                //如果两种选择都不满足要求
                if(s3[i] != s1[p1])   return false;
                //两种选法进入不同路口
                bool flag1 = interleave(s1, p1+1, s2, p2, s3, i+1, vec); //选s1
                if(flag1){
                    vec[p1+1][p2] = 1; //选s1可行
                    return true;
                }   
                bool flag2 = interleave(s1, p1, s2, p2+1, s3, i+1, vec); //选s2
                if(flag2){
                    vec[p1][p2+1] = 1; //选s1可行
                    return true;
                } 
                vec[p1][p2] = 0; //均不可行
                return false;
            }
        } 
        return true;
    }
    bool isInterleave(string s1, string s2, string s3) {
        if(s1.size() + s2.size() != s3.size())  return false;
        vector<vector<int>> vec(s1.size()+1, vector<int>(s2.size()+1, -1)); //初始化为-1
        return interleave(s1, 0, s2, 0, s3, 0, vec);
    }
};

上面的代码for循环可以去掉,思路还是大同小异,效率也差不多:

class Solution {
public:
    // p1, p2, p3分别是s1, s2, s3中下一个待选元素下标
    bool interleave(string &s1, int p1, string &s2, int p2, string &s3, int p3, vector<vector<int>> &vec){
        if(vec[p1][p2] != -1)   return vec[p1][p2];//已经存在记录,直接返回相应值
        // 如果p1 == s1.size() 或者 p2 == s2.size(),则有一个字符串被选完,只需比较另一个字符串和s3的剩余部分是否相等即可
        if(p1 == s1.size()){
            vec[p1][p2] = s2.substr(p2) == s3.substr(p3) ? 1 : 0;
            return s2.substr(p2) == s3.substr(p3);
        }  
        if(p2 == s2.size()){
            vec[p1][p2] = s1.substr(p1) == s3.substr(p3) ? 1 : 0;
            return s1.substr(p1) == s3.substr(p3);
        }  
        if(s3[p3] == s1[p1]){
            if(interleave(s1, p1+1, s2, p2, s3, p3+1, vec)){
                vec[p1+1][p2] = 1;
                return true;
            }
        } 
        if(s3[p3] == s2[p2]){
            if(interleave(s1, p1, s2, p2+1, s3, p3+1, vec)){
                vec[p1][p2+1] = 1;
                return true;
            }
        } 
        vec[p1][p2] = 0; //均不可行
        return false;
    }
    bool isInterleave(string s1, string s2, string s3) {
        if(s1.size() + s2.size() != s3.size())  return false;
        vector<vector<int>> vec(s1.size()+1, vector<int>(s2.size()+1, -1)); //初始化为-1
        return interleave(s1, 0, s2, 0, s3, 0, vec);
    }
};

动态规划

其实这个题更好的解法是动态规划,用动态规划很好理解这个题,具体就不分析了,可以看交错字符串 -官方题解

public:
    bool isInterleave(string s1, string s2, string s3) {
        vector < vector <int> > f(s1.size() + 1, vector <int> (s2.size() + 1, false));
        int n = s1.size(), m = s2.size();

        if (n + m != s3.size()) return false;

        f[0][0] = true;
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= m; ++j) {
                if (i > 0)  f[i][j] = (f[i - 1][j] && s1[i - 1] == s3[ i + j - 1]);
                if (j > 0)  f[i][j] = f[i][j] || (f[i][j - 1] && s2[j - 1] == s3[ i + j - 1]); //如果上一步为true,这一步也为true,所以使用或运算
                //if (j > 0)  f[i][j] |= (f[i][j - 1] && s2[j - 1] == s3[ i + j - 1]);
            }
        }
        return f[n][m];
    }
};

优化,使用滚动数组优化空间复杂度,可以看到上述代码中其实并不需要把每个f的值都存储起来,因为f 的第 i 行只和第i−1 行有关,具体说:

f为二维数组,i对应行,j对应列
f[i][j] = (f[i - 1][j] && s1[i - 1] == s3[ i + j - 1]):这儿用到了上一行的元素f[i - 1][j]
f[i][j] = f[i][j] || (f[i][j - 1] && s2[j - 1] == s3[ i + j - 1]);:这儿用到了这一行的元素f[i][j - 1]

所以可以使用滚动数组优化:

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        vector <int> f(s2.size() + 1, false);
        int n = s1.size(), m = s2.size();

        if (n + m != s3.size()) return false;

        f[0] = true;
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= m; ++j) {
            	//这么写的原因是上一行的值正是保存在f[j],这一边的目的是更新值
                if (i > 0)  f[j] = f[j] && (s1[i - 1] == s3[ i + j - 1]); 
                
                if (j > 0)  f[j] |= (f[j - 1] && s2[j - 1] == s3[ i + j - 1]); 
            }
        }
        return f[m];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值