注意:做算法题的时候,尽量不要用结构体加指针的方法去实现链表,因为每次插入新节点时需要用new来申请内存空间,此操作比较慢,很容易超时,因此,我们最好使用数组来模拟链表,实现比较快。
一 链表
1.单链表
数组模拟单链表的基本思想:
用head表示头节点的下标,e[i]表示节点i的值,ne[i]表示节点i的next指针,即节点i的下一个节点的下标,idx表示当前用到了哪个点。
基本操作:
1. 初始化
void init()
{
head = -1;//初始化为-1,表示空表
idx = 0;//没有使用任何点,故idx为0
}
2. 插入
//将x插入到头节点的后面
void add_to_head(int x)
{
e[idx] = x;//保存节点x
ne[idx] = head;//节点x的指针指向头节点的后继节点
head = idx++;//头节点的指针指向x
}
//将x插入到下标为k的节点后面
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx++;
}
3. 删除
//将下标是k的节点的后面那个节点删除
void remove(int k)
{
ne[k] = ne[ne[k]];
}
4. 遍历单链表
从头节点开始遍历,每次循环使i指向后继节点,只要头节点的下标不为-1,一直循环下去
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
2.双链表
数组模拟双链表的基本思想:
用l[i]表示节点i左边的节点,r[i]表示节点i右边的节点,e[i]表示节点i的值,idx表示已经用到了的哪个点。
基本操作:
1. 初始化:
void init()
{
//0表示左端点,1表示右端点
r[0] = 1;
l[1] = 0;
idx = 2;//因为已经使用了两个点,故初始化为2
}
2.插入操作
//在下标为k的节点右边插入x,如果在下标为k的左边插入x
//直接调用以下函数,将k置为l[k]
void add(int k, int x)
{
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx++;
}
3. 删除操作
//删除第k个点
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
二 栈
用数组模拟栈的基本思想:
用数组stk表示一个栈,tt表示栈顶元素下标(习惯上初始化为0)。
int tt=0;//表示栈顶
int stk[N];//表示栈
1.普通栈
基本操作:
1.1 插入:
stk[++tt] = x;//向栈顶插入x
1.2弹出:
tt--;//弹出栈顶元素
1.3 判断是否为空:
if (tt > 0) return false;判断栈是否为空
else return true;
1.4 取栈顶元素:
stk[tt];
2.单调栈
常见模型:找出每个数左边离它最近的比他大或者小的数
代码模板:
int tt = 0;
for (int i = 0; i < n; i++)
{
while (tt && check(stk[tt], i)) tt–;
if(tt)
//具体问题的逻辑
stk[++tt] = i;
}
举例:
输入几个整数,输出每个整数左边第一个比它小的整数,若不存在,则输出-1;
输入:5
3 4 2 7 5
输出:-1 3 -1 2 2
1.采用数组模拟的栈进行解决
代码实例:
#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e3 + 10, null = 0x3f3f3f3f,M=2*N;
const double eps=1e-6;
int stk[N];
int n,tt;
int main()
{
ios;
cin>>n;
for(int i=0;i<n;i++)
{
int x; cin>>x;
while(tt&&stk[tt]>=x) tt--;//只要栈不空并且栈顶元素的值大于等于x的话,栈顶指针--
if(tt) cout<<stk[tt]<<' ';//如果栈不空的话,说明找到了比x小的数,输出即可
else cout<<"-1"<<' ';//当栈为空的时候也没有找到,说明不存在比x小的数,输出-1
stk[++tt]=x;//每次将x入栈
}
return 0;
}
2.采用STL库中实现好的栈进行解决
代码实例:
#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e3 + 10, null = 0x3f3f3f3f,M=2*N;
const double eps=1e-6;
int n;
stack<int> s;
int main()
{
ios;
cin>>n;
for(int i=0;i<n;i++)
{
int x; cin>>x;
while(s.size()&&s.top()>=x) s.pop();//只要栈不空并且栈顶元素的值大于等于x的话,栈顶指针--
if(s.size()) cout<<s.top()<<' ';//如果栈不空的话,说明找到了比x小的数,输出即可
else cout<<"-1"<<' ';//当栈为空的时候也没有找到,说明不存在比x小的数,输出-1
s.push(x);//每次将x入栈
}
return 0;
}
3.常规做法:(暴力枚举)
#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e3 + 10, null = 0x3f3f3f3f,M=2*N;
const double eps=1e-6;
int n,j;
int a[N];
int main()
{
ios;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++)
{
int flag=0;
for(j=i-1;j>=0;j--)
{
if(a[j]<a[i])
{
flag=1;
break;
}
}
if(flag) cout<<a[j]<<' ';
else cout<<-1<<' ';
}
return 0;
}
三 队列
用数组模拟队列基本思想:
用数组q来表示队列,hh表示队头指针,tt表示队尾指针(习惯上初始化为-1,这点与栈不同,要注意区分)
基本操作:
1.普通队列
1.1 在队尾插入元素:
q[++tt] = x;//在队尾插入元素
1.2 在队头弹出元素:
hh++;//弹出队头元素
1.3 判断队列是否为空:
if (hh <= tt) return false;//判断队列是否为空
eles return true;
1.4 取队头元素:
q[hh];//取出队头元素
1.5 取队尾元素:
q[tt];//取队尾元素
2.单调队列:
常见模型:
找出滑动窗口中的最大值和最小值
模板:
int hh = 0, tt = -1;
for (int i = 0; i < n; i++)
{
while (hh <= tt && check_out(q[hh])) hh++;//判断队头是否划出窗口
while (hh <=tt && check(q[tt], i)) tt--;
q[++tt] = i;
//具体问题的逻辑
}
例题:
滑动窗口:每次在窗口中只能看到k个数字,并且每次滑动窗口向右移动一个位置,求出每一个窗口里的最大值和最小值。
输入:8 3
1 3 -1 -3 5 3 6 7
输出:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
解题思路:
用单调队列存储元素下标,遍历整个序列,枚举所有终点,只要队列不空并且当起点下标大于队头元素下标,说明队头已经划出了窗口,则出队,hh++,只要队列不空并且队尾元素的值大于等于当前元素的值,tt–,要注意一下,只有队列中元素个数大于等于k的时候,才输出队头元素的值,即为最小值。求最大值的话只需要将当队尾元素的值小于等于当前元素的值的时候,tt–。
#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e3 + 10, null = 0x3f3f3f3f,M=2*N;
const double eps=1e-6;
int n,k;
int a[N];
int q[N];
int hh=0,tt=-1;
int main()
{
ios;
cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++)
{
while(hh<=tt&&i-k+1>q[hh]) hh++;
while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
q[++tt]=i;
if(i-k+1>=0) cout<<a[q[hh]]<<' ';
}
cout<<endl;
hh=0,tt=-1;
for(int i=0;i<n;i++)
{
while(hh<=tt&&i-k+1>q[hh]) hh++;
while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
q[++tt]=i;
if(i-k+1>=0) cout<<a[q[hh]]<<' ';
}
return 0;
}
四 KMP算法(模式匹配算法)
算法思路:用指针i和j分解指向主串和模式串中正待比较的字符位置,i从一开始,j从0开始,当产生失配的时候,i不变,j退回到ne[j]的地方重新进行比较,当j退为0的时候,i和j同时加一;
s[]是主串,m是主串长度,t[]是模板串,n是模板串长度
1. 求next数组
//求next数组
for (int i = 2, j = 0; i <= n; i++)
{
while (j && t[i] != t[j + 1]) j = ne[j];
if(t[i]==t[j+1]) j++:
ne[i] = j;
}
2. 匹配过程
for (int i = 1, j = 0; i <= m; i++)
{
while (j && s[i] != t[j + 1]) j = ne[j];
if (s[i] == t[j + 1]) j++;
if (j == n)
{
j = ne[j];
//匹配成功之后的逻辑
}
}
代码实例:
输出模板串在主串中所有出现的位置
输入:3
aba
5
ababa
输出:0 2
#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e3 + 10, null = 0x3f3f3f3f,M=2*N;
const double eps=1e-6;
char s[N], t[N];
int n, m;
int ne[N];
int main()
{
ios;
cin >> n >> t + 1 >> m >> s + 1;//匹配时从下标为1开始匹配,所以读入字符串时从下标为1开始
for (int i = 2, j = 0; i <= n; i++)
{
while (j && t[i] != t[j + 1]) j = ne[j];
if (t[i] == t[j + 1]) j++;
ne[i] = j;
}
for (int i = 1, j = 0; i <= m; i++)
{
while (j && s[i] != t[j + 1]) j = ne[j];
if (s[i] == t[j + 1]) j++;;
if (j == n)
{
cout << i - n << ' ';
j = ne[j];
}
}
return 0;
}