Leetcode 6-10题
Z字形变换
将给定的字符串从上往下、从左到右进行\(Z\)字形排列为\(numRows\)行,在按层序读取为一个新的字符串
\(0123456789\)排列成4行为:
0 6 12 1 5 7 11 13 2 4 8 10 14 3 9 15
可以读取为\(06121571113...\)
这是一个找规律题,题目描述为\(Z\),但其实是一个倒\(N\)。
从上述例子来看,0到5是一个循环,6到11是一个循环。这些循环都是一个等差序列。
并且只有第一行和最后一行是一个等差序列,其余都是两个等差序列交错出现,公差为\(2\times numRows-2\)。
且第\(i\)行的第一个数为\(i\),另一个数为\(2\times numRows-2-i\)。
特判:如果\(numRows=1\),那么只有一行,也就是原字符串。
string convert(string s, int numRows) {
if(numRows == 1) return s; // 特判原字符串
string ans = "";
int p = 2 * numRows - 2, n = s.length();
for(int i = 0; i < numRows; i ++) { // 第i行
if(i == 0 || i == numRows - 1) {
for(int j = i; j < n; j += p)
ans += s[j];
}else {
for(int j = i, k = p - i; j < n || k < n; j += p, k += p) {
if(j < n) ans += s[j];
if(k < n) ans += s[k];
}
}
}
return ans;
}
整数反转
给定32位有符号整数,返回翻转后的整数。若爆
int
则返回0,且不允许使用long long
。
要将1234
翻转成4321
,可以将个位数4
提取出来,然后将原数除10
得到123
。循环这个过程,并将答案的4
乘10
再加得到的个位数。
即ans = ans * 10 + x % 10
。这对于正数显然成立。
对于负数,由于在c++中-4 % 4 = -4
,-4 * 10 + -5 = -45
,所以以上规则也成立。
可以这样判断得到的答案是否会爆int
:
上式必须化简,否则会爆int
。
当\(x\)为正数时,始终只要判断是否超过上限。\(x\)为负数时,始终只要判断是否超过下限即可。
int reverse(int x) {
int ans = 0;
while(x) {
if (x > 0 && ans > (INT_MAX - x % 10) / 10) return 0;
if (x < 0 && ans < (INT_MIN - x % 10) / 10) return 0;
ans = ans * 10 + x % 10;
x /= 10;
}
return ans;
}
字符串转换整数
实现一个能将字符串转换为整数的函数。步骤如下:
- 去除前导空格;
- 检查正负号,如果没有默认为正数;
- 读入数字字符,直到到达末尾或到达非数字字符,并转换为整数;
- 若未读入数字,则答案为0,若超过32位整数范围,小于\(-2^{31}\)固定为\(-2^{31}\),大于\(2^{31}-1\)固定为\(2^{31}-1\)。
和上一题类似,但是这题需要特判-2147483648
的情况。
因为前面判断完正负号之后,后面的数值若为2147483648
会超过int
能表示的最大整数。
但是对于\(-10\times ans-p=INT\_MIN = -2147483648\)是正确的,但是\(10 \times ans+p=INT\_MAX=2147483648\)会爆int
。
所以需要特判这个情况,成为\(-10 \times ans -p = INT\_MIN\),但是这个式子不能化简,因为\(/10\)会过滤2147483647
等情况。
int myAtoi(string s) {
int k = 0, flag = 1, ans = 0;
while(s[k] == ' ') k ++; // 去除前导空格
if(s[k] == '-') flag = -1, k ++;
else if(s[k] == '+') k ++;
while(s[k] >= '0' && s[k] <= '9') {
int p = s[k] - '0';
if(flag == 1 && ans > (INT_MAX - p) / 10) return INT_MAX;
// [-2147483648, 2147483647] 负数需要特判-2147483648的情况
if(flag == -1 && -ans < (INT_MIN + p) / 10) return INT_MIN;
if(-10 * ans - p == INT_MIN) return INT_MIN;
ans = ans * 10 + p;
k ++;
}
return ans * flag;
}
回文数
给定整数,判断是否为回文整数。
可以将整数转换为字符串,然后判断是否为回文串。
bool isPalindrome(int x) {
string str = to_string(x), ss = to_string(x);
reverse(ss.begin(), ss.end());
return str == ss;
}
正则表达式匹配
给定两个字符串,问能否匹配,匹配规则如下:
.
匹配任意单个字符*
匹配零个或多个前面的那一个元素
令\(f(i,j)\)表示\(S[1..i]\)和\(P[1..j]\)是否匹配。显然,\(f(i,0)\)不可能匹配(\(f(0,0)\)除外)。
在不考虑通配符的情况下,f(i,j)=f(i-1,j-1) && (S[i] = P[j])
。
考虑.
通配符时,f(i,j)=f(i-1,j-1) && (P[j] == '.')
考虑*
通配符时,若j >= 2
,那么f(i, j) = f(i, j - 2)
表示匹配零次前面的元素。
如果匹配一次前面的元素,那么f(i, j) = f(i - 1, j - 2) && (s[i] == p[j - 1] || p[j - 1] == '.')
。
如果匹配两次前面的元素,那么f(i, j) = f(i - 2, j - 2) && ...
。
也就是说f(i, j) = f(i, j - 2) + f(i - 1, j - 2) + f(i - 2, j - 2) + ...
。
类似完全背包的转移方式,又有f(i - 1, j) = f(i - 1, j - 2) + f(i - 2, j - 2) + f(i - 3, j - 2) + ...
。
所以f(i, j) = f(i - 1, j) + f(i, j - 2)
。
要使得S[i-1]
和P[j]
能匹配,那么需要s[i]
和p[j - 1]
能成功匹配,也就是说S
的最后一个元素要能和P
的*
之前的元素能匹配。
bool isMatch(string s, string p) {
int m = s.length(), n = p.length();
s = " " + s, p = " " + p;
vector<vector<bool>> f(m + 10, vector<bool>(n + 10));
f[0][0] = true;
for(int i = 0; i <= m; i ++) { // s[0..0]是可以和'a*'匹配的,所以从0开始
for(int j = 1; j <= n; j ++) { // 若P="",那么无法匹配,所以下标从1开始
if(j + 1 <= n && p[j] != '*' && p[j + 1] == '*') continue; // a*需要结合起来使用
// 这里有一个特殊样例
// s = "abc"
// p = "a***abc"
// 显然p[0][2] = true,因为a*可以匹配0次
// 所以多个*视作一个*
if(i > 0 && p[j] != '*')
f[i][j] = f[i - 1][j - 1] && (p[j] == '.' || p[j] == s[i]);
else if(p[j] == '*') // i必须要先大于0才能判断i-1
f[i][j] = f[i][j - 2] || (i > 0 && f[i - 1][j]
&& (p[j - 1] == '.' || p[j - 1] == s[i]));
}
}
return f[m][n];
}