之前收藏了极客时间的算法训练营3期 共21课,计划每一课写博客来记录学习,主要形式为
方法类型1
题1
题解
题2
题解
方法类型2
题1
题解
……
题目大体来自leetcode 和 acwing
主要记录和理解代码,所以基本完全搬运了视频题解代码,
个人学习感受体现在大致思路的总结和注释上。
第一题
8. 字符串转换整数 (atoi)
最终还是选用了扩展数据的范围,用long long 来判断是否超界
边界问题超好玩的啦。
class Solution {
public:
int myAtoi(string s) {
int index = 0;
while (index < s.length() && s[index] == ' ') index++;
int sign = 1;
bool alreadySign = false;
while (index < s.length() && s[index] == '+' || s[index] == '-') {
if (alreadySign) return 0;
sign = s[index] == '+' ? 1 : -1;
index++;
alreadySign = true;
}
long long val = 0;
while (index < s.length() && s[index] >= '0' && s[index] <= '9') {
if (val * 10 + s[index] - '0' > 2147483647) {
if (sign == 1) return 2147483647;
else return -2147483648;
}
val = val * 10 + s[index] - '0';
index++;
}
return sign * val;
}
};
第二题
28. 实现 strStr()
Rabin-Karp
先算哈希值,再比对,
时间复杂度为O(m+n)不过思路可扩展。
class Solution {
public:
int strStr(string haystack, string needle) {
int b = 131, p = 1e9 + 7;
int n = haystack.length();
int m = needle.length();
vector h(n + 1, 0);
// b进制进位, 取模不影响
for (int i = 1; i <= n; i++) {
h[i] = (h[i - 1] * b + haystack[i - 1] - 'a' + 1) % p;
}
long long Hneedle = 0;
long long powBM = 1;
for (char ch : needle) {
Hneedle = (Hneedle * b + ch - 'a' + 1) % p;
powBM = powBM * b % p;
}
for (int l = 1; l <= n - m + 1; l++) {
int r = l + m - 1;
// 相当于做54321 - 54000 = 321
if (((h[r] - h[l - 1] * powBM) % p + p) % p == Hneedle
&& haystack.substr(l - 1, m) == needle)
return l - 1;
}
return -1;
}
};
第三题
125. 验证回文串
传string这些容器的时候还是写上引用最好,传值和传址差的太多了,差点超时。
class Solution {
public:
bool isPalindrome(string s) {
int l = getNext(s, 0);
int r = getPre(s, s.length() - 1);
while (l < r) {
if (!equalsIgnoreCase(s[l], s[r])) return false;
l = getNext(s, l + 1);
r = getPre(s, r - 1);
}
return true;
}
private:
bool equalsIgnoreCase(char a, char b) {
if (a >= 'A' && a <= 'Z') a = a - 'A' + 'a';
if (b >= 'A' && b <= 'Z') b = b - 'A' + 'a';
return a == b;
}
bool isDigitOrLetter (char ch) {
return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9';
}
int getNext(string& s, int i) {
while(i < s.length() && !isDigitOrLetter(s[i])) i++;
return i;
}
int getPre(string& s, int i) {
while (i >= 0 && !isDigitOrLetter(s[i])) i--;
return i;
}
};
第四题
680. 验证回文字符串 Ⅱ
可以删除一个字母,可以用递归的方式,遇到错误选择忽略一次,从两种下一个位置再递归判断。
class Solution {
public:
bool validPalindrome(string s) {
return check(s, 0, s.length() - 1, 1);
}
private:
bool check (string& s, int l, int r, int times) {
if (times < 0) return false;
while (l < r) {
if (s[l] != s[r]) return check(s, l + 1, r, times - 1) || check(s, l, r - 1, times - 1);
l++;
r--;
}
return true;
}
};
第五题
5. 最长回文子串
将哈希值正反算一遍,如果相同说明是回文。
如是,可以二分答案。
反算哈希时关注对应关系。
class Solution {
public:
string longestPalindrome(string s) {
this->s = s;
n = s.length();
preH = vector(n + 1, 0);
sufH = vector(n + 2, 0);
powB = vector(n + 1, 0);
powB[0] = 1;
int ansPos = 0;
int ansLen = 0;
for (int i = 1; i <= n; i++) {
powB[i] = powB[i - 1] * b % p;
preH[i] = (preH[i - 1] * b + s[i - 1] - 'a' + 1) % p;
}
for (int i = n; i >= 1; i--)
sufH[i] = (sufH[i + 1] * b + s[i - 1] - 'a' + 1) % p;
//奇回文串
for (int centre = 0; centre < n; centre++) {
int r = min(centre, n - centre - 1);
int l = 0;
while (l < r) {
int mid = (l + r + 1) / 2;
if (equals(centre - mid, centre + mid, centre - mid, centre + mid))
l = mid;
else
r = mid - 1;
}
if (r * 2 + 1 > ansLen) {
ansPos = centre - r;
ansLen = r * 2 + 1;
}
}
//偶
for (int centre = 0; centre < n; centre++) {
int r = min(centre - 1, n - 1 - centre);
int l = -1;
while (l < r) {
int mid = (l + r + 1) / 2;
if (equals(centre - 1 - mid, centre + mid, centre - 1 - mid, centre + mid))
l = mid;
else
r = mid - 1;
}
if (r * 2 + 2 > ansLen) {
ansPos = centre - 1 - r;
ansLen = r * 2 + 2;
}
}
return s.substr(ansPos, ansLen);
}
private:
string s;
int n;
const int b = 131, p = 1e9 + 7;
vector preH;
vector sufH;
vector powB;
bool equals(int preL, int preR, int sufL, int sufR) {
return (((preH[preR + 1] - preH[preL] * powB[preR - preL + 1]) % p + p) % p)
== (((sufH[sufL + 1] - sufH[sufR + 2] * powB[sufR - sufL + 1]) % p + p) % p);
}
};
第六题
10. 正则表达式匹配
着重理解 a* 这两个字母是一个整体,它可以匹配任意数量的a,包括0,所以起点要多注意。
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.length(), n = p.length();
s = " " + s;
p = " " + p;
vector> f(m + 1, vector(n + 1, false));
f[0][0] = true;
//初值中,a*a*b*c*这种可以配空值,所以要加上
for (int i = 2; i <= n; i += 2) {
if (p[i] == '*') f[0][i] = true;
else break;
}
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++) {
if (p[j] >= 'a' && p[j] <= 'z') {
f[i][j] = f[i - 1][j - 1] && p[j] == s[i];
}
else if (p[j] == '.') {
f[i][j] = f[i - 1][j - 1];
}
else if (p[j] == '*') {
//到此为止
f[i][j] = f[i][j - 2];
//或者本次能配上
if (s[i] == p[j - 1] || p[j - 1] == '.')
f[i][j] = f[i][j] || f[i - 1][j];
}
}
return f[m][n];
}
};
第七题
115. 不同的子序列
f[i][j]表示, 前i个位置,能匹配前j个目标的方案数,将位置向后推,
每个新位置的转移方程为,
要么不用这个新位置来匹配,要么用这个新位置匹配,对应两种转移方法。
class Solution {
public:
int numDistinct(string s, string t) {
int m = s.length();
int n = t.length();
s = " " + s;
t = " " + t;
vector> f(m + 1, vector(n + 1, 0));
//从头到尾都有一种包含“ ”的情况,这是人为插入的,作为起点。
for (int i = 0; i <= m; i++) f[i][0] = 1;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++) {
//不用s[i] 来匹配t[j]
f[i][j] = f[i - 1][j];
//用s[i] 匹配t[j]
if (s[i] == t[j] && f[i][j] < 2147483647 - f[i - 1][j - 1]) {
f[i][j] += f[i - 1][j - 1];
}
}
return f[m][n];
}
};
第八题
KMP
28. 实现 strStr()
注释用作提示,需要有基础理解。
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.length();
int m = needle.length();
vector next(m, -1);//下标从0开始,初值-1,从1开始,0
for (int i = 1, j = -1; i < m; i++) {
//不匹配就倒退j
while (j >= 0 && needle[i] != needle[j + 1]) j = next[j];
//匹配的话可以推
if (needle[j + 1] == needle[i]) j++;
//一直匹配一直推,每一步记录一下。
next[i] = j;
}
for (int i = 0, j = -1; i < n; i++) {
//不匹配就倒退j到相应的前缀位置
while (j >= 0 && needle[j + 1] != haystack[i]) j = next[j];
//匹配的话向后推j
if (j + 1 < m && needle[j + 1] == haystack[i]) j++;
//成功推到结尾说明匹配成功
if (j + 1 == m) return i - (m - 1);
}
return -1;
}
};