acwing菜鸟闯关记(二)

acwing菜鸟闯关记(二)

单链表

struct Node
{
    int date;
    struct Node * next
}
 
new Node()

单链表源数据结构如代码。
运用数组模拟单链表速度更快。

void init(){//初始化
    head = -1;
    idx = 0;
}

void add_to_head(int val){//加到头节点后
    e[idx] = val;
    ne[idx] = head;
    head = idx;
    idx ++;
}

void add(int val,int k){//加到k节点后
    e[idx] = val;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx++;
}

void remove(int k){//移除k节点
    ne[k] = ne[ne[k]];
}

思路:e[]表示值,ne[]表示下一节点。

双链表

void init(){
    //0左端点,1右端点
    r[0] = 1;
    l[1] = 0;
    idx = 2;
}
//下标k右边插入x
void add(int k ,int x){
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;//先后顺序
    r[k] = idx;
    idx++;
}
//左边插入调用 add(l[k],x)
//删除第k个
void remove(int k){
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

模拟栈

int m;
const int N = 100010;

int stk[N],tt = 0;

//插入
stk[++ tt] == x;
//弹出
tt --;
//判断是否为空
if(tt >0) not empty
else empty
//栈顶
cout<<stl[tt];

栈用数组,最高位就是栈顶。数组覆盖与数组往上加。

模拟队列

/*
 * 队尾插入元素
 * 队头hh,队尾tt
 */
int q[N],hh,tt = -1;
//进队列
q[++tt] = x;
//弹出
hh++;
//判空
if(hh <= tt)    not empty
else empty
//队头
cout<<q[hh];

单调栈

栈中元素按照递增或者递减顺序排列。每个元素只会进栈一次,进栈时会把破坏单调性的栈内元素弹出。

 while(tt && stk[tt]>=x) tt--;
 if (tt) cout<<stk[tt]<<" ";
 else    cout<<"-1"<<" ";
 stk[++tt] = x;

单调队列

滑动窗口,维护一个双向队列,遍历序列,仅当一个元素可能成为某个区间最值时才保留它。

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

    if (i>=k-1) cout<<a[q[hh]]<<" ";
}

KMP

//构建next数组
//i是用来扫描后缀的,j是用来扫描前缀
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++;//匹配j就加1
    ne[i] = j;//存下来
}
//寻找匹配值
for(int i = 1,j = 0;i <= m;i++)
{
// 当j为0或者不匹配时,就往前跳,而跳的位置需要查上一位的next数组
    while(j && s[i] != p[j + 1])
        j = ne[j];
//跳出while循环有两种,如果它们匹配的话,j就加1,再继续匹配
    if(s[i] == p[j + 1])
        j++;
    if(j == n)
    {
        printf("%d ",i - n);//说明匹配成功了,输出下标
        j = ne[j];
//注意一定要执行这一步,因为不知道字符串后面还有没有可以匹配的,否则就会不动了
    }
}

Trie树

const int N = 1000010;
int son[N][26],cnt[N],idx;//son子节点,最多连26条边
// cnt为以当前这个点为结尾的单词有多少个,
// idx 存当前用到了哪个下标
// 下标为0的点,又是根节点又是空节点
//全局变量不定义就是0
char str[N];
void insert(char str[])
{
    int p = 0;//p就是一个指针,刚开始指向root
    //由于字符串结尾是"\0",可以通过str[i]来判断是否到字符串末尾
    for(int i = 0;str[i];i++)
    {
        int u = str[i] - 'a';//得到元素
        //判断节点是否有此元素,如果没有就创建一个
        if(!son[p][u])
            son[p][u] = ++idx;//给节点编号
        p = son[p][u];//指向子节点
    }
    cnt[p]++;//以这个字符结束的单词次数加1,即给单词做标记
}

int query(char str[])
{
    int p = 0;//同样的,刚开始指向root
    for(int i = 0;str[i];i++)// 判断是否到字符串末尾
    {
        int u = str[i] - 'a';// 得到要查询的节点
        //遍历树,如果没有此节点说明单词不存在
        if(!son[p][u])
            return 0;
        p = son[p][u];//有这个节点的话,就令p指向这个节点,然后看看这个节点下面是否有下一个元素的节点
    }
    return cnt[p];//返回单词出现次数
}

最大异或对

^为位异或

void insert(int x){
    int p = 0;
    for (int i = 30; i>=0; i--) {//i>=0 等价于~i
        int &s = son[p][x>>i&1];//取X的第i位的二进制数是什么  x>>k&1(前面的模板)
        if (!s) s = ++idx;
        p = s;
    }
}
int query(int x){
    int res = 0,p = 0;
    for (int i = 30; i >=0 ; i--) {
        int s = x >> i & 1;
        if (son[p][!s]){
            res+= 1<<i;//第i位变1,也就是加上高位1;res =res*2+1
            p = son[p][!s];
        }
        else{
//            res += 0 <<i;//可省略  res = res*2+0
            p = son[p][s];
        }
    }
    return res;
}

并查集

1、合并两个集合-----》father接father
2、查询两个元素是否在一个集合-----》father是否一样
时间复杂度近似O(1)

const int N = 100010;
int n,m;
int p[N],d[N];//d[N]为到根节点距离
//查询x的祖宗节点 + 路径压缩
int find(int x){
    if (p[x] != x){
        int t = find(p[x]);
        d[x] += d[p[x]];
        p[x] = t;
    }
    return p[x];
}

堆排序

是最高效的优先级队列。堆通常是一个可以被看做一棵完全二叉树的数组对象。
手写堆的的常用操作有几个

  1. 插入一个数
  2. 求集合当中的最小值
  3. 删除最小值
  4. 删除任意一个元素
  5. 修改任意一个元素

堆是用一个一维数组存储的,根节点存到下标为1的位置,节点的左儿子存到下标为2x的位置;右儿子存到下标为2x+1的位置。
小根堆性质:父节点比它的左右儿子的值小。
down操作

void down(int x){//注意变量  不能改变,变了就坏了
    int t = x;
    if (x * 2 <=hsize && h[x*2] <h[t] ) t = x*2;
    if (x * 2+1 <=hsize && h[x*2+1] <h[t] ) t = x*2+1;
    if (x!=t){
        swap(h[x],h[t]);
        down(t);
    }
}

up操作

void up(int x){//注意变量  不能改变,变了就坏了
    while (x/2 && h[x/2]>h[x]){
        swap(x/2,x);
        x/=2;
    }
}

heap[size]为数组最后一个元素;
heap[1]为根节点

哈希

哈希表最主要的作用把一个比较庞大的空间/值域映射到比较小的空间。
第一种为开放寻址法。
数组最好取质数。

//查找坑位
int query(int x){
    int k = (x%N+N)%N;
    while(h[k]!=null &&h[k]!=x){
        k++;
        if (k == N) k = 0;
    }
    return k;
}

if (op[0] == 'I'){//插入
            int k = query(x);
            h[k] = x;
        }
        else{//查找
            int k = query(x);
            if (h[k]!=null)    puts("Yes");
            else puts("No");
        }

第二种为拉链法。

void insert(int x){//插入,单链表
    int k = (x%N+N)%N;

    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx;
    idx++;
}

bool query(int x){
    int k = (x%N+N)%N;//查询
    for (int i = h[k]; i != -1 ; i = ne[i]) {
        if (e[i] == x)  return true;
    }
    return false;
}

memset(h,-1,sizeof h);//初始化

字符串哈希
本质上是前缀和与权值相加结合。
P一般取131或13331,数组取质数

//前缀和求差
int n,m;
char str[N];
ULL h[N],p[N];
ULL get(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
//构造前缀和
for (int i = 1; i <= n; ++i) {
    p[i] = p[i-1] * P;
    h[i] = h[i-1]*P + str[i];
}

加油!拼搏百天!
HEBUT 作者:陈冰yunshangjin
邮箱:suchenbin@163.com 可以发邮件进行交流

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值