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

来源:互联网 发布:52mac网可靠吗 编辑:程序博客网 时间:2024/05/18 14:23

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也没有完成,估计开学到了学校更难静下心来刷题,算法之路果然是遥遥无期啊:(

原创粉丝点击