加载中...

栈数组模拟+单调窗口

-1是空集
head头节点
e[] 某个点的值 下标是某个点
ne[] 是下个指针
idx存当前已经用到的点
单链表

单链表 idx表示的是第几个插入的数(idx++) 而不是插入的位置

#include <iostream>

using namespace std;

const int N = 100010;


// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
    head = -1;//含义:头节点在下标为1的位置 表示 初始化
    idx = 0;//idx下一个新建立的点在下标0处
}

// 将x插到头结点,那么这个x就是头结点,原本的头结点变成第二个结点
void add_to_head(int x)//head存的是链表第一个点的下标
{
    e[idx] = x;
    ne[idx] = head;
     head = idx ++ ;//idx的值是x
}

// 将x插到下标是k的点后面
void add(int k, int x)//k是下标
{
    e[idx] = x;
  ne[idx] = ne[k];
 ne[k] = idx ++ ;//ne是
//                idx新点的下一个点在原先k点下一个位置
}

// 将 下标是k的点后面一个点 注意是下标后面的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

int main()
{
    int m;
    cin >> m;

    init();

    while (m -- )
    {
        int k, x;
        char op;

        cin >> op;
        if (op == 'H')
        {
            cin >> x;
            add_to_head(x);
        }
        else if (op == 'D')
        {
            cin >> k;
            if (!k) head = ne[head];//这里常常都漏了
            else remove(k - 1);
        }
        else
        {
            cin >> k >> x;
            add(k - 1, x);
        }
    }

    for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
    cout << endl;

    return 0;
}


双链表哦

#include <iostream>

using namespace std;

const int N = 100010;

int m;
int e[N], l[N], r[N], idx;//存值 左和右 idx  少了个head

// 在节点k的右边插入一个数x
void insert(int k, int x)
{
    e[idx] = x;//第idx个小标的值设为
    l[idx] = k, r[idx] = r[k];//修改idx idx的左边变成k idx的右边变为原来k的右边
    l[r[k]] = idx, r[k] = idx ++ ;//修改原数组 右边的左边变为k 而原数的右边变为k
}

// 删除节点k
void remove(int k)
{
    l[r[k]] = l[k];//
    r[l[k]] = r[k];
}

int main()
{
    cin >> m;

    // 0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;//idx直接从2开始


    while (m -- )
    {
        string op;
        cin >> op;
        int k, x;
        if (op == "L")
        {
            cin >> x;
            insert(0, x);//0是左端点所以一直插在最左端的右边就可以 只要最后不输出0就好了
        }
        else if (op == "R")
        {
            cin >> x;
            insert(l[1], x);
        }
        else if (op == "D")
        {
            cin >> k;
            remove(k + 1);
        }
        else if (op == "IL")
        {
            cin >> k >> x;
            insert(l[k + 1], x);
        }
        else
        {
            cin >> k >> x;
            insert(k + 1, x);
        }
    }

    for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';//从0开始 一直遍历到1为止
    cout << endl;

    return 0;
}

表达式求值https://www.acwing.com/problem/content/3305/

#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>

using namespace std;

stack<int> num;//num存数组
stack<char> op;//op存操作符

void eval()
{
    auto b = num.top(); num.pop();//b==末尾的数字,注意a-b b在后面
    auto a = num.top(); num.pop();//a是倒数第二个
    auto c = op.top(); op.pop();//取出一个操作符
    int x;
    if (c == '+') x = a + b;
    else if (c == '-') x = a - b;
    else if (c == '*') x = a * b;
    else x = a / b;
    num.push(x);//存进去num
}

int main()
{
    unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};//哈希表存+-*/快速找到
    string str;
    cin >> str;//获得表达式
    for (int i = 0; i < str.size(); i ++ )//从前面到后面遍历表达式
    {
        auto c = str[i];//一个个字符
        if (isdigit(c))//发现是数字
        {
            int x = 0, j = i;
            while (j < str.size() && isdigit(str[j]))//j从i开始一步一步,使用while如果下一个依然是数字就可以把二位数也存进去
                x = x * 10 + str[j ++ ] - '0';
            i = j - 1;//如果不是数字了就记得回来,是j-1是因为下面还会i++
            num.push(x);
        }
        else if (c == '(') op.push(c);//左括号放进去
        else if (c == ')')//右括号
        {
            while (op.top() != '(') eval();//一直做运算知道是op栈顶是左括号
            op.pop();//删去左括号
        }
        else//剩下的情况是加减乘除
        {
            while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();//现在输入的运算符优先级比栈顶的优先级低或相等 先计算栈顶的运算符
            //因为(没有计算所以要特判
            op.push(c);//再放入操作符
        }
    }
    while (op.size()) eval();//如果还有操作符就从右往左操作
    cout << num.top() << endl;
    return 0;
}


单调栈https://www.acwing.com/problem/content/832/

题目满足 当ai>=aj &&ai>aj ai永远不会被用到可以输出 所以可以直接输出端点

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

const int N = 1e5+10;
int  n;

int q[N],tt;

int main()
{   ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    int x;
    for (int i = 0; i < n; i ++ ){
        cin>>x;
        while(tt&&q[tt]>=x) tt--;
        if(tt) cout << q[tt]<<" ";//栈顶永远放最小且下标最大的数
        else cout << "-1"<<" ";
        
        q[++tt]=x;
        
    }
    return 0;
}

单调队列https://www.acwing.com/problem/content/156/

队里存储的是数组下标 判断有没有超出窗口方便 直接输出端点

#include <iostream>

using namespace std;

const int N = 1000010;

int a[N], q[N];

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    int hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;//窗口一个元素的下标小了 所以需要移除 移出第一个元素  如果不确定移除多少元素 可以写while

        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;//剩余元素中 将队列中的元素比将要加入的元素小的 以后都不会有机会输出了 为了保持hh的元素是最小值 原队列中小的元素减少,
        q[ ++ tt] = i;//加入队列,因为上面队列有可能减到0个元素,所以要加入 否则下面输出错误

        if (i >= k - 1) printf("%d ", a[q[hh]]);//遍历到的第i个位置 超过k个元素 输出顶部
    }

    puts("");

    hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ )
    {
        if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;

        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[ ++ tt] = i;

        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }

    puts("");

    return 0;
}

模拟散列表https://www.acwing.com/video/266/

哈希+单链表(head->h[k])

#include <cstring>
#include <iostream>

using namespace std;

const int N = 100003;

int h[N], e[N], ne[N], idx;//把单链表的head变成h数组

void insert(int x)
{
    int k = (x % N + N) % N;
    e[idx] = x;
    ne[idx] = h[k];//ne[] 和h[] 和 e[] 都是存储idx 不过这次ne 存的是上次h【】的idx 而这次的h[]存的才是这次的idx 
    h[k] = idx ++ ;
}

bool find(int x)
{
    int k = (x % N + N) % N;
    for (int i = h[k]; i != -1; i = ne[i])//从头开始 往ne遍历 
        if (e[i] == x)//这里的i是e数组[]的下标
            return true;

    return false;
}

int main()
{
    int n;
    scanf("%d", &n);

    memset(h, -1, sizeof h);//在单链表里head设置为-1 这里数组就是全-1

    while (n -- )
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);

        if (*op == 'I') insert(x);
        else
        {
            if (find(x)) puts("Yes");
            else puts("No");
        }
    }

    return 0;
}

#include <cstring>
#include <iostream>

using namespace std;

const int N = 200003, null = 0x3f3f3f3f;//这个数大于1e9

int h[N];//只需要开一个数组

int find(int x)
{
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x)//位置上有人但是不等于我们要放入的值说明存储了其他值
    {
        t ++ ;
        if (t == N) t = 0;//回到0重新开始找
    }
    return t;//位置上没人或者等于k
}

int main()
{
    memset(h, 0x3f, sizeof h);//开始设为0x3f

    int n;
    scanf("%d", &n);

    while (n -- )
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        if (*op == 'I') h[find(x)] = x;
        else
        {
            if (h[find(x)] == null) puts("No");
            else puts("Yes");
        }
    }

    return 0;
}

最长单调子序列2https://www.acwing.com/problem/content/898/

法一:
构造一个单调栈 (从0到i 这个操作保证了单调)对每个a[i]在这个栈找第一个小于她的数 把他插在这个数的后面 那么从栈底到a[i]插入位置就是 原数组中能够以a[i]结尾的最长长度 
优点 能够输出最长子序列是谁
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int a[N];
int q[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);

    int len = 0;
    for (int i = 0; i < n; i ++ )
    {
        int l = 0, r = len;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (q[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        q[r + 1] = a[i];
    
        for(int i=0;i<=r+1;i++){
            cout << q[i]<<" ";
        }cout<<endl;
    }

    printf("%d\n", len);

    return 0;
}
输入
10
1 3 4 2 7 5 6 8 3 3
输出
0 1 
0 1 3 
0 1 3 4 
0 1 2 
0 1 2 4 7 
0 1 2 4 5 
0 1 2 4 5 6 
0 1 2 4 5 6 8 
0 1 2 3 
0 1 2 3 
6





#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(void) {
    int n; cin >> n;
    vector<int>arr(n);
    for (int i = 0; i < n; ++i)cin >> arr[i];

    vector<int>stk;//模拟堆栈
    stk.push_back(arr[0]);

    for (int i = 1; i < n; ++i) {
        if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈 
            stk.push_back(arr[i]);
        else//替换掉第一个大于或者等于这个数字的那个数
            *lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];//通过改变栈里的某个元素 这里不是正常理解的后进后出栈!
    }
    cout << stk.size() << endl;
    return 0;
}

输入
6
3 1 2 1 8 5 6 (最长子序列 1 2 5 6)
输出
栈  1 2 5 6 (虽然不是子序列 但是仍然是这个长度)
使用贪心 希望每一个栈为size长度 为长度的数组 的最后一个数都能最小 长度为i的递增子串中,末尾元素最小的是stk[i] 这样以后我也有更多机会拓展。
比如 
1 3 4 2 3 4 7 5 6 8 在看到第3个数的时候 有机会放入第一个3 4 后面还有个2 3 4 所以要把4 换掉 (即1 2 3 4 5 6 8  )
真正的序列
1 3 4 2  7 5 6 8 3 3尽管最后最长序列没有2 但是通过尝试这种可能我们可以在维持最长长度的同时 还能找到更多的可能性让数组变长(1 3 4  5 6 8 )
 
posted @ 2022-02-18 23:54  liang302  阅读(36)  评论(0编辑  收藏  举报