模板题---1.5(单调栈,单调队列,kmp,manachar)


这里记录几个基本的数据结构算法

1.单调栈

要点:

  1. 数据结构是栈
  2. 栈要维护单调性

这就是单调栈的基本定义

举个例子:

(栈底)1 3 5 7 (栈顶)

<-------这就是一个单调栈😋

当然,定义很简单,但是用的时候不一定很顺利,相反,用的时候非常的绕圈子,但是首先定个基调,这个算法非常简单。

拿几个题目作为模板题来练习:
acwing单调栈
在这里插入图片描述

在这里插入图片描述
这道题是单调栈最简单的题。
目的是:找到左边第一个小于当前的数
那么这种情况是满足的:数组从左到右严格递增
这样就可以保证每个数左边第一个小于自己的数被快速找到,因为答案就是左边第一个数。

比如:

1 2 3 4 5

1,2,3,4,5左边第一个小于自己的数就是他们本身左边第一个数。

最后,目的就是去构建并且维护这样一个单调的数组。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 1e5 + 10;

int a[N];
int b[N];
stack<int>st;
int n;


int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
    {
        while (!st.empty() && st.top() >= a[i])  //如果栈不空,且栈顶元素大于等于当前元素,将栈顶弹出(维护栈单调性)
               st.pop();
        if (st.empty())   //如果栈空,说明a[i]左边没有比它小的元素
            b[i] = -1;
        else             //否则有,记录
            b[i] = st.top();    
        st.push(a[i]);    
    }

    for (int i = 1; i <= n; i++)
        cout << b[i] << " ";
    return 0;
}

也可以不需要数组b,用一些小技巧:

-----00

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 1e5 + 10;

int a[N];
stack<int>st;
int n;


int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
    {
        while (!st.empty() && st.top() >= a[i])  //如果栈不空,且栈顶元素大于等于当前元素,将栈顶弹出(维护栈单调性)
               st.pop();
        int t=a[i];
        if(st.empty())
            a[i]=-1;
        else
            a[i]=st.top();
        st.push(t);
            
    }

    for (int i = 1; i <= n; i++)
        cout << a[i] << " ";
    return 0;
}

继续练习,这个题掌握了,单调栈基本上就一知半解了😋

洛谷里面有几个题:
1.P1901 发射站
在这里插入图片描述
这个题目翻译一下:
分别找到比第i个数左边第一个比第i个数高的数和右边第一个高的数。

那么分为两个问题
1.找到第i个数左边第一个比它高的数
2.找到第i个数右边第一个比它高的数

那么这样的序列可以一次找到左边第一个比它高的数:

5 4 3 2 1

那么这样的序列可以一次找到右边第一个比他高的数:

1 2 3 4 5

如果这样来拆分,实际上就是套用了两次模板。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;

const int N = 1e6 + 10;

struct node
{
	int h, v,i;
}A[N];

int energy[N][2];//energy[i][0]表示左边第一个比i高的位置下标,energy[i][1]表示右边第一个比i高的下标
int ans[N];  
stack<node>st;

int n;

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int a, b;
		cin >> a >> b;
		A[i].h = a, A[i].v = b, A[i].i = i;
	}

	//分别找出比当前数左边第一个高的和右边第一个高的
	//左边第一个高
	for (int i = 1; i <= n; i++)  //只要维护的是一个单调递减数列,那么就是合理的
	{
		while (!st.empty() && st.top().h<= A[i].h)st.pop();  //如果当前能量塔高度大于等于栈顶,栈顶元素弹出
		if (st.empty())
			energy[i][0] = 0;
		else
			energy[i][0] = st.top().i;
		st.push(A[i]);
	}
	//先清空栈重复利用
	while(!st.empty())
		st.pop();
	//右边第一个高,实际上就是反过来找左边第一高,
	for (int i = n; i >= 1; i--)
	{
		while (!st.empty() && st.top().h <= A[i].h)st.pop();  //如果当前能量塔高度大于等于栈顶,栈顶元素弹出
		if (st.empty())
			energy[i][1] = 0;
		else
			energy[i][1] = st.top().i;
		st.push(A[i]);
	}

	int Max = -99999;
	
	for (int i = 1; i <= n; i++)   //由于每个能量塔都会传播能量到左右,所以用一个数组ans记录所有接收的能量
	{
		ans[energy[i][0]] += A[i].v;
		ans[energy[i][1]] += A[i].v;
	}
	for (int i = 1; i <= n; i++)  //最后找到接受能量最多的
		Max = max(Max, ans[i]);
	cout << Max << endl;
	return 0;
}

2.单调队列

和单调栈大同小异,维护队列的单调性
滑动窗口

#include<iostream>
#include<cstring>
using namespace std;

const int N=1e6+10;

int a[N];   ///模拟数组
int q[N];
int tt,hh;

int n,k;

int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++)
        cin>>a[i];
    
    hh=0,tt=-1;  //hh是队头(左),tt是队尾(右)
    for(int i=0;i<n;i++)  //维护单调递增队列
    {
        if(hh<=tt&&q[hh]<i-k+1)hh++;   //如果窗口左边下标大于队头下标,那么队头弹出,hh右移一个单位
        while(hh<=tt&&a[q[tt]]>=a[i])tt--;  //维护单调递增队列
        q[++tt]=i;
        if(i>=k-1)
            cout<<a[q[hh]]<<" ";
    }
    cout<<endl;
    
    hh=0,tt=-1;  //hh是队头(左),tt是队尾(右)
    for(int i=0;i<n;i++)  //维护单调递增队列
    {
        if(hh<=tt&&q[hh]<i-k+1)hh++;   //如果窗口左边下标大于队头下标,那么队头弹出,hh右移一个单位
        while(hh<=tt&&a[q[tt]]<=a[i])tt--;  //维护单调递增队列
        q[++tt]=i;
        if(i>=k-1)
            cout<<a[q[hh]]<<" ";
    }
    cout<<endl;
    return 0;
}

3.kmp算法

首先定一个基调,这是一个异常烦人的算法,和那个manachar算法一样折磨人。
这题注意,字符下标要从1开始,否则ne[j]会出现j==-1的清空,数组不允许下标是-1
题目:KMP模板
学习链接,任何人都可以看懂:董晓老师算法

#include<iostream>
using namespace std;
const int N=1e6+10;
char S[N],P[N];
int ne[N];
int n,m;


int main()
{
    cin>>n;
    scanf("%s",P+1);
    cin>>m;
    scanf("%s",S+1);
    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]=j;
    }
    
    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-n<<" ";
            j=ne[j];
        }
    }
    return 0;
}

4.manachar算法

这个问题的来源是解决这样一个问题:给定一个字符串S,找出这个字符串里面的最长回文子串。
比如abbaba ,其中最长的回文子串是abba,所谓回文串,就是正着反着读是一样的。而子串一定要保证连续的。

题目:最长回文子串

这个题和kmp扩展算法基本一样。

class Solution {
public:
    string longestPalindrome(string s) 
    {
        //第一步构造新字符串
        string ss="";
        ss+="@#";
        for(int i=0;i<s.length();i++)
        {
            ss+=s[i];
            ss+="#";
        }
        //构造一个数组d[N]记录所有字符的回文半径 
        //初始化1,d[i]=1表示以i为中心的字符串的回文半径为1
        vector<int>d(ss.length(),1); 
        //开始manachar算法
        for(int i=1,boxl=0,boxr=0,x=0;i<ss.length();i++)
        {
            int y=2*x-i;   //y是元素ss[i]关于x的对称点
            if(boxr>=i)d[i]=min(d[y],boxr-i+1);
            while(ss[i+d[i]]==ss[i-d[i]])d[i]++;
            if(boxr<i+d[i]-1)   //当前盒子的右端点大于老盒子的右端点
            {
                x=i;
                boxl=i-d[i]+1;
                boxr=i+d[i]-1;
            }
        }

        int a,b;  //记录最长回文串的下标和回文半径
        for(int i=1;i<ss.length();i++)
        {
            if(d[i]>a)
            {
                a=d[i];
                b=i;
            }
        }
        string ans="";
        for(int i=b-a+1;i<=b+a-1;i++)
        {
            if(ss[i]!='#')
                ans+=ss[i];
        }
        return ans;

    }
};

不用leetcode的话这样写

#include<iostream>
#include<cstring>
using namespace std;

const int N = 1e7 + 10;

int d[N];
string s, ss,ans;

string manachar(string &s)
{
	ss = ans = "";
	ss += "$#";
	for (int i = 0; i < s.length(); i++)
	{
		ss += s[i];
		ss += "#";
	}
	for (int i = 0; i < ss.length(); i++)
		d[i] = 1;
	for (int i = 1, l = 0, r = 0, x = 0; i < ss.length(); i++)
	{
		int y = 2 * x - i;
		if (r >= i)d[i] = min(d[y], r - i + 1);
		while (ss[i + d[i]] == ss[i - d[i]])d[i]++;
		if (r < i + d[i] - 1)
		{
			x = i;
			l = i - d[i] + 1;
			r = i + d[i] - 1;
		}
	}
	int index=0, Maxlen=-1;
	for (int i = 1; i < ss.length(); i++)
	{
		if (Maxlen < d[i])
		{
			Maxlen = d[i];
			index = i;
		}
	}

	for (int i = index - Maxlen + 1; i <= index + Maxlen - 1; i++)
	{
		if (ss[i] != '#')
			ans += ss[i];
	}
	puts(" ");
	return ans;
}
int main()
{
	cout << "输入字符串:";
	cin >> s;
	cout << "最长回文子串是:" << endl;
	cout << manachar(s) << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值