LintCode 解题记录17.8.19 字符串处理6

LintCode Scramble String

判断给定两个等长的字符串是不是攀爬字符串。
对于给定的字符串,我们通过不断将其分割成两个非空子字符串的方式构建一棵二叉树。我们可以选择任一个非叶子节点然后交换其孩子节点,再重新从底部攀爬上去形成一个新字符串,那么该新字符串就与原字符串形成了攀爬字符串。

思路1

由于二叉树是递归的建立的,那么我们就可以尝试从递归的角度来解决这个问题。对于两个字符串树treeA和treeB,分别记其左右子树为treeA(B)_left,那么可以得到如下关系:
isScramble(treeA, treeB) = isScramble(treeA_left, treeB_left) && isScramble(treeA_right, treeB_right) ||
isScramble(treeA_left, treeB_right) && isScramble(treeA_right, treeB_left)。
用别人的话来说,就是把字符串s1分割成s11和s12,把字符串s2在同样位置分割成s21和s22,如果s11与s21,s12与s22都为攀爬字符串或者s11与s22,s12与s21为攀爬字符串,那么字符串s1和s2就同为攀爬字符串。

代码1
    bool isScramble(string s1, string s2) {
        //注意递归终结的条件
        if (s1 == s2) return true;
        string tmp1 = s1, tmp2 = s2;
        sort(tmp1.begin(), tmp1.end());
        sort(tmp2.begin(), tmp2.end());
        if (tmp1 != tmp2) return false;
        int len = s1.size();
        for (int i = 1; i < len; i++) {
            string s11 = s1.substr(0, i);
            string s12 = s1.substr(i);
            string s21 = s2.substr(0, i);
            string s22 = s2.substr(i);
            if (isScramble(s11, s21) && isScramble(s12, s22)) return true;
            s21 = s2.substr(len-i);
            s22 = s2.substr(0, len-i);
            if (isScramble(s11, s21) && isScramble(s12, s22)) return true;
        }
        return false;
    }
思路2

还有一种解法是动态规划,为什么可以这样说呢?因为发现判断s1和s2是不是攀爬字符串时用到的是s1子串和s2子串的信息,即可以理解为用到了历史信息,所以可以考虑用动态规划来解决。我们考虑状态dp[i][j][len]代表从字符串s1的i位开始,字符串s2的第j位开始,长度为len的字符串是不是攀爬字符串。那么根据之前的递归思路,我们可以写出递推关系:
dp[i][j][len] = dp[i][j][k]&&dp[i+k][j+k][len-k] || dp[i][j+len-k][k] && dp[i+k][j][len-k]。1 <= k < len,k可以理解为将字符串一分为二的那个位置。
注意dp[i][j][k]这些存储的历史信息,这里我们要把len循环放在最外层。边界情况就是len==1的情况,需要先单独处理一下。

代码2
bool isScramble(string s1, string s2) {
        // write your code here
        if (s1.size() != s2.size()) return false;
        int n = s1.size();
        vector<vector<vector<bool> >> dp(n, vector<vector<bool>>(n, vector<bool>(n+1, false)));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dp[i][j][1] = s1[i] == s2[j];
            }
        }
        for (int len = 2; len <= n; len++) {
            for (int i = 0; i <= n-len; i++) {
                for (int j = 0; j <= n-len; j++) {
                    for (int k = 1; k < len; k++) {
                        if (dp[i][j][k]&&dp[i+k][j+k][len-k] || 
                            dp[i][j+len-k][k] && dp[i+k][j][len-k]) {
                                dp[i][j][len] = true;
                            }
                    }
                }
            }
        }
        return dp[0][0][n];
    }

LintCode String to IntegerII

实现atoi函数,遇到非法表示则返回0,最后的整数如果溢出int型则返回INT_MAX或INT_MIN。

思路

先介绍一下atoi函数的处理思路: 首先跳过前导空格,然后判断一下符号有没有(+或-),遇上第一个数字字符开始转换,遇到第一个非数字字符便停止转换。这里采用的顺讯转换可以学习一下,因为平时我都是逆序+权来转换的。

代码
    int atoi(string str) {
        // write your code here
        bool positive = true;
        int i = 0;
        while (str[i] == ' ' && i < str.size()) i++;
        if (i == str.size()) return 0;
        if (str[i] == '+' || str[i] == '-') {
            positive = str[i] == '+';
            i++;
        }
        int res = 0;
        while (i < str.size()) {
            if (isdigit(str[i])) {
                res = res*10 + (str[i++]-'0'); //原数乘10然后加上字符,顺序转换。
            }
            else break;
            if (res < 0) break; //向上溢出int型范围,此时res就变成了负数
        }
        //if (res < 0) return positive? INT_MAX : INT_MIN;
        return res < 0 ? (positive ? INT_MAX : INT_MIN) : (positive ? res : -res);
    }
注意

这题是基础而且很重要,面试有可能被问到。

LintCode Valid Number

给定一个字符串,问你该字符串是否是一个有效的数字,包含整数,小数,以及科学计数法。

思路

刚开始拿到这道题的时候有点感觉像atof的实现,于是按照思路进行处理,提交,发现有什么情况没考虑到就开始小修小补代码,后来也提交通过了。百度了一下,发现有更简洁的想法与做法。
首先明确一共有几种符合题意的字符串:数字、’.’、e,正负号,还有空格。常规思路先跳过前导空格和正负号,然后开始处理,便利到的结果有如下几种:
1)遍历到数字: 那么就跳过,遍历下一字符。
2)遍历到小数点:根据有一些测试样例可以知道,”1.”与”.1”都是符合题意的,所以对于小数点有如下要求,一是第一次出现,二是不能出现在指数e的后面。
3)遍历到指数: 同理,指数必须是第一次出现,而且指数前后必须要有数字出现。
4)遍历到正负号:由于起始的正负号已经处理过了,所以这里的正负号只能出现在指数的后面。
5)其他字符: 显然就不符合题意了,后来发现存在末尾0的情况,所以在刚开始的时候需要跳过前导0和末尾0。
综上所述,对于前面四种情况都需要相应的bool变量来标示。

代码
    bool isNumber(string s) {
        // write your code here
        int i = 0, e = s.size()-1;
        while (i <= e && isspace(s[i])) i++;
        while (e >= i && isspace(s[e])) e--;
        if (i > e) return false;
        if (s[i] == '+' || s[i] == '-') i++;
        bool num = false; // represent a num
        bool dot = false; // represent a dot
        bool exp = false; // represent eorE
        while (i <= e) {
            if (isdigit(s[i])) {
                num = true;
            } else if (s[i] == '.') {
                if (exp || dot) return false;
                dot = true;
            } else if (s[i] == 'e' || s[i] == 'E') {
                if (exp || !num) return false;
                exp = true;
                num = false;
            } else if (s[i] == '+' || s[i] == '-') {
                if (s[i-1] != 'e' || s[i-1] != 'E') return false;
            } else 
                return false;
            i++;
        }
        return num;
    }
注意

后来了解到这道题还可以用 有限状态机 或者 正则表达式来做,代码简单的一笔。
有限状态机:http://blog.csdn.net/kenden23/article/details/18696083
正则表达式:http://blog.csdn.net/fightforyourdream/article/details/12900751

LintCode Wildcard Matching

判断两个字符串是不是匹配。这道题和Regular Expression Matching很像,不过后者的”“代表前一字符的序列,而此处的”“代表任一字符串,个人感觉,这道题要简单一点。

思路1

递归和动态规划。递归超时了,所以这里提及一下动态规划。我们维护一个状态dp[i][j]代表字符串s的前i个字符和字符串的前j个字符是不是匹配的。有了之前那一题的经验,这里我们只考虑p中会出现符号。
那么显然有两种情况,一是s[i-1] == p[j-1] || p[j-1] == ‘?’,代表第i-1位和第j-1位匹配,那么dp[i][j] = dp[i-1][j-1]。
第二种情况是p[j-1] == ‘‘,这种情况下dp[i][j] = dp[i][j] || dp[i-k][j-1],(0 <= k <= i) 也就是说把这个”“分别想象成空字符串,一个字符,两个字符,然后从历史存储信息中如何得到当前状态即可。
然后考虑边界情况,显然dp[0][0] = 1,然后提及一下这里的i需要从0开始遍历,为什么?因为如果s为空字符串,而p只有”*”也是匹配的,而如果p为空字符串,s为非空字符串是无论如何都不可能匹配的。

代码1
    bool isMatch(string s, string p) {
        // write your code here
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m+1, vector<bool>(n+1, false));
        dp[0][0] = 1;
        // i = 0?
        for (int i = 0; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (i > 0 && (s[i-1] == p[j-1] || p[j-1] == '?')) {
                    dp[i][j] = dp[i-1][j-1];
                }
                if (p[j-1] == '*') {
                    for (int k = 0; k <= i; k++) {
                        if (dp[i-k][j-1]) {
                            dp[i][j] = true;
                            break;
                        }
                    }
                }
            }
        }
        return dp[m][n];
    }

类似正则表达式匹配的思想,其实k只需要取空字符串和一个字符串即可。

代码2
    bool isMatch(string s, string p) {
        // write your code here
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m+1, vector<bool>(n+1, false));
        dp[0][0] = 1;
        // i = 0?
        for (int i = 0; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (i > 0 && (s[i-1] == p[j-1] || p[j-1] == '?')) {
                    dp[i][j] = dp[i-1][j-1];
                }
                if (p[j-1] == '*') {
                    dp[i][j] = (i > 0 && dp[i-1][j]) || dp[i][j-1];
                }
            }
        }
        return dp[m][n];
    }
思路2

发现了网上的贪心做法,虽然暂时没觉得贪在哪里。

代码3
    bool isMatch(string s, string p) {
        // write your code here
        int pcurr = 0, scurr = 0, pstar = -1, sstar = -1;
        while (scurr < s.size()) {
            if (s[scurr] == p[pcurr] || p[pcurr] == '?') {
                scurr++;
                pcurr++;
            } else if (p[pcurr] == '*') {
                pstar = pcurr++;
                sstar = scurr;
            } else if (pstar != -1) {
                pcurr = pstar+1;
                scurr = ++sstar;
            } else return false;
        }
        while (p[pcurr] == '*') pcurr++;
        return pcurr == p.size();
    }

感想

暑假快结束了,说好的刷完LintCode也没有完成,估计开学到了学校更难静下心来刷题,算法之路果然是遥遥无期啊:(

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值