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也没有完成,估计开学到了学校更难静下心来刷题,算法之路果然是遥遥无期啊:(
- LintCode 解题记录17.8.19 字符串处理6
- LintCode 解题记录17.8.4 字符串处理2
- LintCode 解题记录 17.8.7 字符串处理3
- LintCode 解题记录17.8.8 字符串处理4
- LintCode解题记录17.8.9 字符串处理5
- LintCode 解题记录 字符串处理1.0 17.7.29
- LintCode 解题记录 17.8.30 两个指针
- LintCode 解题记录 17.6.19~17.6.25
- LintCode 解题记录17.4.27
- LintCode解题记录17.4.28
- LintCode解题记录 17.5.3
- LintCode 解题记录 2017.6.3
- LintCode 解题记录 7.11 ~ 7.16
- LintCode 解题记录 Matrix专题
- LintCode解题记录17.9.9
- LintCode解题记录-Catalan Number
- LintCode 解题记录17.10.21
- LintCode 解题记录 17.11.11
- Linux下进程间通信概述
- 在着手准备做一个开发项目(web前端),需要做哪些准备?
- 计算机中的定点数和浮点数
- Leet Code OJ 1. Two Sum [Difficulty: Easy]
- MYSQL+vs2010+Navicat基础笔记
- LintCode 解题记录17.8.19 字符串处理6
- (已解决)雷柏v500在Ubuntu系统下键位映射错误
- 【cas】sso和cas概述
- 常见的动态规划问题分析与求解
- 个人随笔和笔记:响应式布局meta声明
- iOS时间戳
- map reduce lambda 区别 用法 结合使用
- C++资源
- 浅析逆元&逆元的蛋用(完全版)