必刷题
文章目录
滑动窗口
#include<iostream>
using namespace std;
const int N = 1000010;
//队列q中装的是下标【重点】
int a[N], q[N], hh = 0, tt = -1;
int main() {
int n, k;
cin >> n >> k;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
//求最小值,维护单增队列【重点】
for (int i = 0; i < n; i++) {
//先检查是否滑出队列【重点】【出队和入队的顺序不能调换】
if (i - q[hh] + 1 > k) hh++;
//再插入新元素【重点】
while (tt >= hh && a[i] < a[q[tt]]) tt--;
q[++tt] = i;
if (i >= k - 1) cout << a[q[hh]] << " ";
}
cout << endl;
//求最大值,维护单减队列
tt = -1, hh = 0;
for (int i = 0; i < n; i++) {
//先检查是否滑出队列
if (i - q[hh] + 1 > k) hh++;
//再插入新元素
while (tt >= hh && a[i] > a[q[tt]]) tt--;
q[++tt] = i;
if (i >= k - 1) cout << a[q[hh]] << " ";
}
return 0;
}
字符串匹配
朴素法:
#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
char p[N], s[M];
int main() {
int n, m;
//【重要】字符串的起始下标为1。
cin >> n >> p + 1 >> m >> s + 1;
//【重要】j表示已经匹配成功的对数。
//【重要】s[i]和p[j + 1]进行比较,而不是s[i]和p[j + 1]。
for (int i = 1, j = 0; i <= m; i++) {
if (s[i] != p[j + 1]) {
i = i - j;
j = 0;
}
else if (s[i] == p[j + 1]) j++;
if (j == n) {
i = i - j + 1; //【注意】此处i回退的长度和上面i回退的长度不相等。
j = 0;
cout << i - 1 << " ";
}
}
return 0;
//总结:
//朴素字符串匹配方法中,j每次都会回退到0,i也会根据j的值不断回退。
}
KMP算法:
#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
char p[N], s[M];
int ne[N];
int main() {
int n, m;
cin >> n >> p + 1 >> m >> s + 1;
//求解next数组
//【重要】i从2开始
for (int i = 2, j = 0; i <= n; i++) {
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j++;
//【重要】ne[i]表示p字符串[1, i - 1]的【最小前后公共缀】的长度是j。
ne[i] = j;
}
//KMP匹配过程
for (int i = 1, j = 0; i <= m; i++) {
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j++;
if (j == n) {
cout << i - j << " ";
j = ne[j];
}
}
return 0;
}
前缀树
#include<iostream>
#include<string>
using namespace std;
struct Node {
Node* next[26];
int cnt;
};
int main() {
Node* root = new Node(); //【重要】root必须是指针,且root中不装字母。
int n;
cin >> n;
while (n--) {
char op;
cin >> op;
if (op == 'I') {
string str;
cin >> str;
int n = str.size();
Node* p = root;
for (int i = 0; i < n; i++) {
int idx = str[i] - 'a';
if (!p->next[idx]) p->next[idx] = new Node();
p = p->next[idx];
p->c = str[i];
if (i == n - 1) p->cnt++;
}
}
if (op == 'Q') {
string str;
cin >> str;
int n = str.size();
Node* p = root;
for (int i = 0; i < n; i++) {
int idx = str[i] - 'a';
if (!p->next[idx]) {
cout << 0 << endl;
break;
}
p = p->next[idx];
if (i == n - 1) cout << p->cnt << endl;
}
}
}
return 0;
}
类似题目:
背包问题
01背包问题
【特点】:每个物品只有一个。
#include<iostream>
using namespace std;
const int N = 1010;
//f[i][j]表示i行j列的最大价值,也就是表示选择前i个物品(不一定都选),且当前体积恰好为j时的最大价值
int f[N][N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j]; //【重要】状态转移
if (j >= v)
//f[i][j]也就是f[i - 1][j]
f[i][j] = max(f[i][j], f[i - 1][j - v] + w); //【重要】状态转移
}
}
cout << f[n][m] << endl;
return 0;
}
压缩二维数组f。
#include<iostream>
using namespace std;
const int N = 1010;
int f[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j--) //【注意】体积是倒着循环的。
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
完全背包问题
#include<iostream>
using namespace std;
const int N = 1010;
int f[N][N];
//f[i][j]表示选择前i个物品(不一定都选,也不知道每一种的个数),且体积恰好是j的最大价值。
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= v) {
for (int k = 1; k * v <= j; k++) {
f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);
//思考:为什么不是f[i][j] = max(f[i - 1][j], f[i - 1][j - k * v] + k * w);
//因为f[i][j] != f[i - 1][j]。f[i][j]是随着k的变化而动态变化的。
}
}
}
}
cout << f[n][m] << endl;
return 0;
}
压缩版:
#include<iostream>
using namespace std;
const int N = 1010;
int f[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = 1; j <= m; j++)
if (j >= v)
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
类似题目:
动态规划系列
【思路指导】:
状态表示
状态计算
爬火山类型
状态表示:a[i] [j]表示从[1,1]到[i, j]的步数。
状态计算:a[i] [j] += max(a[i - 1] [j], a[i - 1] [j - 1])。
特别的:i == 1时,a[i] [j] += a[i - 1] [j]
i == j 时,a[i] [j] += a[i - 1] [j - 1]
#include<iostream>
using namespace std;
const int N = 510;
int a[N][N], f[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
scanf("%d", &a[i][j]);
memset(f, -10001, sizeof f);
f[1][1] = a[1][1];
for (int i = 2; i <= n; i++)
for (int j = 1; j <= i; j++) {
f[i][j] = max(f[i - 1][j], f[i - 1][j - 1]) + a[i][j];
}
int res = 0;
for (int i = 1; i <= n; i++)
res = max(res, f[n][i]);
cout << res << endl;
return 0;
}
最长递增序列
状态表示:f[i]表示前i项最长递增序列的长度。
状态计算:f[i] = max(f[i], f[j] + 1),j ∈ [1, i]。
#include<iostream>
using namespace std;
const int N =1010;
int a[N], f[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
f[i] = 1;
for (int j = 1; j < i; j++) {
if (a[i] > a[j] && f[i] <= f[j])
f[i] = f[j] + 1; //状态转移
}
}
int res = 0;
for (int i = 1; i <= n; i++) res = max(res, f[i]);
cout << res << endl;
return 0;
}
类似题目:
不同路径 II
深搜法(超时):
class Solution {
public:
void dfs(vector<vector<int> >& obs, int n, int m, int x, int y,
vector<vector<int> >& res) {
res[x][y]++;
if (x + 1 < n && !obs[x + 1][y])
dfs(obs, n, m, x + 1, y, res);
if (y + 1 < m && !obs[x][y + 1])
dfs(obs, n, m, x, y + 1, res);
}
int uniquePathsWithObstacles(vector<vector<int>>& obs) {
int n = obs.size(), m = obs[0].size();
if (obs[0][0] == 1) return 0;
vector<vector<int> > res(n, vector<int>(m, 0));
dfs(obs, n, m, 0, 0, res);
return res[n -1][m - 1];
}
};
动态规划:
状态表示:f[i] [j]表示从[0, 0]到[i, j]的路线总数。
状态计算:f[i] [j] = f[i - 1] [j] + f[i] [j -1]
特别的:i == 0时,f[i] [j] = f[i] [j - 1]
j == 0时,f[i] [j] = f[i - 1] [j]
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obs) {
int n = obs.size(), m = obs[0].size();
vector<vector<int> > f(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (obs[i][j]) f[i][j] = 0;
else if (!i && !j) f[i][j] = 1;
else if (!i) f[i][j] = f[i][j - 1];
else if (!j) f[i][j] = f[i - 1][j];
else if (!obs[i][j]) f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[n - 1][m - 1];
}
};
解码方法
状态表示:f[i]表示下标<=i的字符串的解码方法。
状态计算:f[i] == f[i - 1] + f[i - 2] 字符串长度大于3且f[i - 1]f[i]组成的数∈[10, 26]
f[i] == f[i - 1]
f[i] == f[i - 2]
f[i] == 0
【难点】状态计算的情况比较多
class Solution {
public:
int numDecodings(string s) {
if (s[0] == '0') return 0;
int n = s.size();
vector<int> f(n);
f[0] = 1;
for (int i = 1; i < n; i++) {
if (s[i] != '0') f[i] = f[i - 1];
if (i >= 1) {
int num = (s[i] - '0') + (s[i - 1] - '0') * 10;
if (num >= 10 && num <= 26 && i >= 2) f[i] += f[i - 2];
if (num >= 10 && num <= 26 && i == 1) f[i]++;
}
}
return f[n - 1];
}
};
最大连续子序列
编辑距离
状态表示:f[i] [j]表示将第一个字符串(简称“一串”)的前i个字符转换成第二个字符串(简称“二串”)的前j个字符所需要的最小步数。
状态计算:
1. f[i][j] = f[i][j - 1] + 1:一串前i个字符与二串前j - 1个字符匹配,再给一串添加一个字符
2. f[i][j] = f[i - 1][j] + 1:一串前i - 1个字符与二串前j个字符匹配,再删除一串的第i个字符
3. f[i][j] = f[i - 1][j - 1]:一串的前i - 1个字符和二串的前j - 1个字符匹配,且一串的第i个字符和二串的第j个字符相等。
4. f[i][j] = f[i - 1][j - 1] + 1:一串的前i - 1个字符和二串的前j - 1个字符匹配,且一串的第i个字符和二串的第j个字符不相等。
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.size(), m = word2.size();
vector<vector<int>> f(n + 1, vector<int>(m + 1));
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int j = 0; j <= m; j++) f[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f[i][j] = min(f[i][j - 1], f[i - 1][j]) + 1;
if (word1[i - 1] == word2[j - 1]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
}
return f[n][m];
}
};
vector<vector> f(n + 1, vector(m + 1));
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int j = 0; j <= m; j++) f[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f[i][j] = min(f[i][j - 1], f[i - 1][j]) + 1;
if (word1[i - 1] == word2[j - 1]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
}
return f[n][m];
}
};