第四章---串 板子
1、KMP
板子
只返回第一处的位置KMP
class KMP {
public:
//参数:主串、模式串
//必须知道:主串、模式串内容,主串模式串大小
int kmp(string source, string item) {
vector<int> next = find_next(item);//第一步:获取Next数组
int n = source.size();
int n_item = item.size();
int k = -1;//模式串判断位置指针
for (int i = 0; i < n; i++) {//从零开始,到主串末尾
while (k > -1 && source[i] != item[k + 1]) {//k>-1且模式串和主串不匹配才会回溯
k = next[k];
}
if (source[i] == item[k + 1]) {//匹配后,模式串处理指针加一
k++;
}
if (k == n_item - 1) {//结束条件,
return i;
}
}
return -1;
}
private:
//模式串匹配模式串
//参数:模式串
//必须知道:模式串内容,模式串大小
vector<int> find_next(string item) {
int n = item.size();
vector<int> next(n, -1);//建立next,第一项固定-1
int k = -1;//模式串处理位置指针
for (int i = 1; i < n; i++) {
while (k > -1 && item[i] != item[k + 1]) {//原理同上
k = next[k];
}
if (item[i] == item[k + 1]) {
k++;
}
next[i] = k;//存一下每个位置的next值
}
return next;
}
};
找到所有的位置的KMP
就是在找完一次之后,重置k=-1即可,统计总共成功几次。
#include <iostream>
#include <string>
#include <stack>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> find_next(string item) {//自己匹配自己
int n = item.size();
vector<int>next(n, -1);
int k = -1;
for (int i = 1; i < n; i++) {//第一个位置默认-1
while (k > -1 && item[i] != item[k + 1]) {//先考虑回溯,k>-1(下界)并且匹配不上-->回溯
k = next[k];
}
if (item[i] == item[k + 1]) {
k++;
}
next[i] = k;
}
return next;
}
int KMP(string source, string item) {
vector<int> next = find_next(item);
int n = source.size();
int n_item = item.size();
int k = -1;
int ans = -1;
for (int i = 0; i < n; i++) {//枚举主串元素
while (k > -1 && source[i] != item[k + 1]) {
k = next[k];
}
if (source[i] == item[k + 1]) {
k++;
}
if (k == n_item - 1) {
k == -1;//这里就是,每匹配完一次,k重置,重新再次匹配,ans++
ans += 1;
}
}
return ans == -1 ? -1 : ans + 1;
}
例题
力扣:检查子树(本题还是一道经典的换角度看待二叉树,进而解决子树问题)
2、朴素方法模式匹配
板子
#include <iostream>
using namespace std;
int main() {
//前期准备
int size_a = 5;//主串
int size_m = 3;//模式串
int *a = new int[size_a];
int *m = new int[size_m];
int ans = -1;
for (int i = 0; i < size_a; i++) {
cin >> a[i];
}
for (int i = 0; i < size_m; i++) {
cin >> m[i];
}
//从此处开始朴素的匹配过程
for (int i = 0; i < (size_a - size_m + 1); i++) {//共匹配size_a - size_m +1次
int j = 0;//每次模式串都是从头开始
int k = i;//记录一下主串从何处开始
for (; j < size_m; j++, k++) {//二者一起前进
if (a[k] != m[j]) {//匹配失败,马上退出,更新 k j
break;
}
}
if (j == size_m) {//匹配成功,则j走到最后
ans = i + 1;
break;
}
}
cout << ans;
return 0;
}
3、滑动窗口
经典习题:力扣:424. 替换后的最长重复字符
思路
1、首先不考虑k,即令k=0,是最朴素滑动窗口问题
2、回到本题,相当于允许窗口内存在不同的元素,利用K次机会来反悔,把"和主体部分不同的元素进行更改",来使他们成为都一样的连续的串。
3、为了知道窗口内非主题元素个数,所以一定要维护一个cnt用以记录主体元素个数,(right-left+1)表示窗口大小,(right-left+1-cnt)代表需要替换的元素个数,如果>k说明此次扩张失败,left++
注意:
1、每次边界移动,都会造成窗口内元素个数变化,分别是右边界加元素,左边界减元素
2、元素变化都会造成cnt变化,注意判定
3、先元素个数变化,再边界变化
板子
class Solution {
public:
int characterReplacement(string s, int k) {
int word[26] = {};//动态维护窗口内元素个数
int left = 0, right = 0, cnt = 0;//滑窗准备(左右指针),以及维护窗口内最多的元素个数
int n = s.size();
while (right < n) {//正确理解right指针,它指向本次要加入到窗口的元素,此时该元素未加入窗口,所以窗口大小为right-left+1
cnt = max(cnt, ++word[s[right] - 'A']);//先变化窗口内元素个数,再看看是否会影响到cnt
if (right - left + 1 - cnt > k) {//发现窗口大小大了
if (word[s[left] - 'A'] == cnt) {//顺序正好反过来,看看是否影响了cnt
cnt--;
}
word[s[left] - 'A']--;//在变化元素个数
left++;//左指针右移
}
right++;//代表这个元素加入窗口结束&&接着指向下一个待处理的元素
}
return right - left;
}
};
4、C语言操作字符串四大函数
四大函数介绍
//字符串的赋值运算
strcpy(str1, str2); //让str1 = str2
//字符串的比较运算
strcmp(str1, str2); //比较字典序,如果str1大于str2就返回正数,等于就返回0,小于就返回负数
//字符串的拼接操作
strcat(str1, str2); //将str2拼接到str1后面 //单词concat:合并多个字符串
//求字符串的长度
int len = strlen(str1);
操作思路
1、因为c语言没有string,所以只能用这四大库函数操作串。
2、在操作的时候,就把一个一个的串,当成一个一个的"数字",用这四个函数,来实现“比较,交换(复制),拼接,求长度”等功能
例题:2021软件专硕
#include <iostream>
#include <string>
#include <stack>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
void function_three() {//把一个一个的串当成
char str[10][10] = { "abc","def","ajsd","rtdfv","ajsdf","treyry" };
bool flag = true;
while (flag) {
flag = false;
for (int i = 0; i < 5; i++) {
if (strcmp(str[i], str[i + 1]) > 0) {//串之间比较大小,用strcmp
flag = true;
char tmp[10] = " ";//交换两个串,使用strcpy,使用临时串保存一个
strcpy(tmp, str[i]);
strcpy(str[i], str[i + 1]);
strcpy(str[i + 1], tmp);
}
}
}
return;
}
5、回文串问题
找到所有的回文串:中心扩展法
例题
思路:本质上控制变量
1、为了找到所有回文子串,一定得按照某种规律枚举才行,有序的枚举
2、任何的回文串,都存在回文中心,中心要么是一个元素,要么是两个元素
3、所以说,按顺序枚举串里面的所有元素,因为任何元素都可能是回文中心,然后考虑单中心和双中心的问题
4、然后双指针,从中心开始判断,并向两边一起扩展(注意越界判定),一旦遇到两边不同元素的情况,停止即可,枚举下一个中心
代码
class Solution {
public:
int countSubstrings(string s) {
int ans = 0;
int n = s.size();
for (int i = 0; i < n; i++) {//枚举所有回文中心
for (int j = 0; j < 2; j++) {//0代表单中心,1代表双中心
int l = i;//左右指针,所中心开始判断
int r = i + j;
//越界判定+两边一致(不一致直接停止,枚举下一个中心)
while (l >= 0 && r < n&&s[l--] == s[r++]) {
ans++;
}
}
}
return ans;
}
};