C++ 必刷题

必刷题

滑动窗口

#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];
}
};


  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值