算法基础(数据结构)

算法基础(数据结构)

单链表

826.单链表

  • 用数组形式来模拟单链表,在算法题中,因为 new ListNode 操作比较慢,容易引起超时,所以用数组来代替指针比较快速
int e[N], ne[N], idx, head; 
// e[] 为链表的数值数组, idx 为链表下标, ne[] 为链表当前的下一个下标, head 为链表头结点指向的下标
// 初始化操作
void init(){
    head = -1, idx = 0; // 当链表到结尾是 head 指向下标为-1, 链表的第一个数值的下标为 idx=0
}
// 在链表的头部添加结点 数值为x
void addHead(int x){
    e[idx] = x;
    ne[idx] = head;
    head = idx++;
}
// 删除第k个插入的数的后面的数 (第k个插入的数的下标为k-1)
void deleteK(int k){
    ne[k] = ne[ne[k]];
}
// 在第k个插入的数后面插入一个数 x
void insertK(int k, int x){
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx++;
}

双链表

827.双链表

const int N = 100010;

int  e[N], l[N], r[N], idx, k; //e[N]为节点,l[N]为左节点, r[N]为右节点
void init()  //初始化
{
    r[0] = 1;
    l[0] = 1;
    r[1] = 0;
    l[1] = 0;
    idx = 2;
}

void insert(int k, int x) // 在右边插入操作
{
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
    idx++;
}

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

822.模拟栈

const int N = 1e5 + 10;
int s[N]; //s数组为栈
int tt = -1; // tt为数组下标

void push(int s[], int x){ // 压入栈操作
    s[++tt] = x;
}
void pop(int s[]){ // 弹出栈操作
    tt--;
}
bool empty(int s[]){ // 判断栈是否为空
    return tt > -1 ? false : true;
}
int query(int s[]){ // 查询栈顶元素
    return s[tt];
}

3302. 表达式求值

  • 两个核心关键点

    1. 双栈,一个操作数栈,一个运算符栈

    2. 运算法优先级,栈顶运算符和即将入栈的运算符的优先级的比较 -----------> 如果栈顶的运算符优先级低,新运算符直接入栈;如果栈顶的运算符优先级高或者和即将入栈的运算符等级相等,先出栈计算,新运算符再入栈

    3. 这个方法的时间复杂度为O(n),整个字符串只需要扫描一遍。

      运算符有±/()~^&都没问题,如果共有n个运算符,会有一个nn的优先级表。

    stack<int> num;
    stack<char> op;
    //优先级表
    unordered_map<char, int> h{ {'+', 1}, {'-', 1}, {'*',2}, {'/', 2} };
    void eval()//求值
    {
        int a = num.top();//第二个操作数
        num.pop();
        int b = num.top();//第一个操作数
        num.pop();
        char p = op.top();//运算符
        op.pop();
        int r = 0;//结果 
        //计算结果
        if (p == '+') r = b + a;
        if (p == '-') r = b - a;
        if (p == '*') r = b * a;
        if (p == '/') r = b / a;
        num.push(r);//结果入栈
    }
    
    int main()
    {
        string s;//读入表达式
        cin >> s;
    
        for (int i = 0; i < s.size(); i++)
        {
            if (isdigit(s[i]))//数字入栈
            {
                int x = 0, j = i;//计算数字
                while (j < s.size() && isdigit(s[j]))
                {
                    x = x * 10 + s[j] - '0'; // 将s[j] 从char 变为 int
                    j++;
                }
                num.push(x);//数字入栈
                i = j - 1;
            }
            //左括号无优先级,直接入栈
            else if (s[i] == '(')//左括号入栈
            {
                op.push(s[i]);
            }
            //括号特殊,遇到左括号直接入栈,遇到右括号计算括号里面的
            else if (s[i] == ')')//右括号
            {
                while(op.top() != '(')//一直计算到左括号
                    eval();
                op.pop();//左括号出栈
            }
            else
            {
                while (op.size() && h[op.top()] >= h[s[i]])//待入栈运算符优先级低,则先计算
                    eval();
                op.push(s[i]);//操作符入栈
            }
        }
        while (op.size()) eval();//剩余的进行计算
        cout << num.top() << endl;//输出结果
        return 0;
    }
    

队列

829. 模拟队列

hh = tt = -1; // hh为头下标, tt 为尾下标
// 插入元素
s[++tt] = x;
// 弹出元素
s[hh++] = x;

单调栈

830. 单调栈

// 单调栈---找到左边离它最近的比他小的数
const int N = 1e5 + 10;

int s[N], tt = -1;

bool empty(int s[]){
    if(tt > -1) return false;
    return true;
}
/*
思路: 先判断栈是否为空
1. 如果为空:输出-1,将输入的数压入栈中;
2. 如果不为空:分两种情况
    1. 如果输入的数大于栈顶元素,输出栈顶元素,并将输入的数压入栈中
    2. 如果输入的数小于或者等于栈顶元素,将栈顶元素弹出直到栈为空或者栈顶元素小于输入元素为止
*/
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++ ){
        int x;
        cin >> x;
        while(s[tt] >= x && !empty(s))
                tt--;
        if(empty(s)) cout << -1 << " ";
        else cout << s[tt] << " ";
        s[++tt] = x;
    }
    return 0;
}

单调队列

154.滑动窗口

const int N = 1e6 + 10;

int q[N], res[N]; //q[]存储输入数组,**res[]存储进入队列的数组的下标**

int main()
{
    int n, k;
    cin >> n >> k;
    for (int i = 0; i < n; i ++ ){
        cin >> q[i];
    }
    int hh =0, tt = -1;
    for (int i = 0; i < n; i ++ ){
        if(hh <= tt &&  i - k + 1 > res[hh]) hh++;  // 当队列还有数并且队头元素下标小于i-k+1时,弹出队头元素
        while(hh <= tt && q[i] < q[res[tt]]) // 当队列还有数 并且 当即将入队元素小于队尾元素时,将队尾元素删去,使得队列为单调递增队列
        {    
            tt--;
        }
        res[++tt] = i; //每次都将下表为i的元素压入队列
        if(i - k + 1 >= 0) cout << q[res[hh]] << " ";
    }
    puts("");
    hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ ){
        if(hh <= tt && i - k + 1 > res[hh]) hh++;
        while(hh <= tt && q[i] > q[res[tt]]) tt--;
        res[++tt] = i;
        if(i - k + 1 >= 0) cout << q[res[hh]] << " ";
    }
    
    return 0;
}

KMP

831.kmp字符串

const int N = 1e5 + 10;
const int M = 1e6 + 10;

int n, m;
char s[N], p[M];
int ne[N]; // next数组会报错,将next数组变为ne数组


int main()
{
    cin >> n >> p + 1 >> m >> s + 1; //从下标为1开始输入
    //求子串的ne数组
    for (int i = 2, j = 0; i <= n; i ++ ){ //ne[1] = 0,从下标2开始计算ne数组
        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 - j << " ";
            j = ne[j];
        }
    }
    
    
    return 0;
}

Trie

  • 解决字符串出现多少次,需要先构建好trie树后在进行查询操作
  • 一般两个操作:1. 插入操作 2. 查询操作

835.tried字符串统计

const int N = 1e5 + 10;

int son[N][26]; // son[a][b] 数组表示a的下一个字母
int idx, cnt[N]; // idx为新增结点下标,cnt[N]表示 输入字符串出现的次数
char str[N];

//插入操作
void insert(char str[]){ 
    int p = 0;
    for (int i = 0; str[i]; i ++ ){
        int u = str[i] - 'a';
        if(!son[p][u]) son[p][u] = ++idx; //新增结点
        p = son[p][u];  //令p为字符串的下一个字母
    }
    cnt[p]++;
}
//查询操作
int query(char str[]){
    int p = 0;
    for (int i = 0; str[i]; i ++ ){
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;//没有找到字符串
        p = son[p][u];
    }
    return cnt[p];
}

143.最大异或树

const int N = 1e5 + 10;
const int M = 3.2e6 + 10; 

int a[N];
int son[M][2], idx = 0; //idx 是结点下标

void insert(int x){ //构建异或树
    int p =0;
    for (int i = 30; i >= 0; i -- ){
        int u = x >> i & 1; //判断x的第i位数是0还是1
        if(!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
}

int query(int x){ //查询最大异或值
    int p = 0;
    int res = 0;
    for (int i = 30; i  >= 0; i -- ){
        int u = x >> i & 1;
        if(son[p][!u]){
            p = son[p][!u];
            res = res * 2 + 1;
        }else{
            p = son[p][u];
            res = res * 2;
        }
    }
    return res;
}

int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++ ){
        cin >> a[i];
        insert(a[i]);
    }
    int res = 0;
    for (int i = 0; i < n; i ++ ){
        res = max(res, query(a[i]));
    }
    cout << res;
    return 0;
}

并查集

  • 主要解决集合的 合并 和 查询操作

836.合并集合

const int N = 1e5 + 10;
int p[N];  // p[]是父亲节点

int find(int x)  // 并查集  (find函数查找元素所属并查集)
{
    if (p[x] != x) p[x] = find(p[x]); // 递归查询x的祖先节点 --- 路径压缩
    return p[x];
}


int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
        p[i] = i;  //每个数的父亲节点都是自己
    char op;
    while (m -- ){
        int a, b;
        cin >> op >> a >> b;
        if(op == 'M') p[find(a)] = find(b); // 合并操作:将a的祖先节点的父节点指向b的祖先节点让a,b变为一个集合
        else {
            if(find(a) == find(b))
                cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
    
    return 0;
}

837.连通快中的数量

int find(int x){
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}


int main()
{
    int n,m;
    cin >> n >> m;
    for (int i = 0; i < n; i ++ ){
        p[i] = i;
        countI[i] = 1;
    }
    
    while(m--){
        string op;
        int a, b;
        cin >> op;
        if(op == "C"){
            cin >> a >> b;
            if(find(a) != find(b)){
                countI[find(a)] += countI[find(b)];  // 连接操作前将集合的总数的下表count更新 --- 主要操作
                p[find(b)] = find(a);  // 将集合根节点更新
            }
        }
        else if(op == "Q1"){
            cin >> a >> b;
            if(find(a) == find(b)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
        else{
            cin >> a;
            cout << countI[find(a)] << endl;
        }
    }

824 食物链

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5e4 + 10;
int p[N];
int d[N]; // d[]代表节点到根节点的距离,模3等于0 为 A 类,等于1为B类,等于2为C类;B吃A,C吃B,A吃C

int find(int x)  // 并查集
{
    if (p[x] != x){
        int t = find(p[x]); //先保存x的根节点
        d[x] += d[p[x]];  // x到根节点的距离等于当前节点到px距离加上px到根节点距离
        p[x] = t;
    }
    return p[x];
}


int main()
{
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ){
        p[i] = i;
    }
    int res = 0;
    while(k--){
        int a, x, y;
        cin >> a >> x >> y;
        int px = find(x), py = find(y);
        if(x > n || y > n) res++;
        else {
            if(a == 1){
                if(px == py && (d[x] - d[y]) % 3) res++;
                else if(px != py){
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else{
                if(x == y) res++;
                else if(px == py && (d[x] - d[y] - 1) % 3) res++;
                else if(px != py){
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }
    }
    cout << res;
    return 0;
}

堆排序

838 堆排序

堆排序操作:
1. 建立堆 — 从 n/2 位置开始执行down操作到1为止

void down(int u, int cnt){ //down操作--递归操作 判断左右儿子中有没有比自己更小的数然后交换之,随后从交换的位置处递归down操作
    int t = u;
    if(2 * u <= cnt && h[t] > h[2 * u]) t = 2 * u;
    if(2 * u + 1 <= cnt && h[t] > h[2 * u + 1]) t = 2 * u + 1;
    if(t != u)
    {
        swap(h[t], h[u]);
        down(t, cnt);
    }
2. 插入数  --- 在对尾处插入数,随后执行up操作,和down操作类似,看父节点是否比自己小,然后交换
3. 删除任意数  --- 令h[k] = h[size--] ,随后执行down操作,和up操作,两者只会有一个被执行
4. 修改任意一个数 -- h[k] = x,随后执行down操作,和up操作,两者只会有一个被执行

模拟堆

主要解决映射问题,建立一个ph和hp数组,ph[i]对应第i个插入堆得数现在在堆中的下标,hp[i]对应堆得下标为i得数是第几个插入堆的。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;
int h[N], ph[N], hp[N]; // ph映射到堆,hp堆到映射

void heap_swap(int a, int b) // 在交换堆得数值的同时,也要交换映射
{
    swap(ph[hp[a]], ph[hp[b]]);
    swap(hp[a],hp[b]);
    swap(h[a],h[b]);
}

void down(int p, int cnt)
{
    int t = p;
    if(t * 2 <= cnt && h[p] > h[t * 2]) p = t * 2;
    if(t * 2 + 1 <= cnt && h[p] > h[t * 2 + 1]) p = t * 2 + 1;
    if( t != p)
    {
        heap_swap(p, t);
        down(p, cnt);
    }
}

void up(int p)
{
    int t = p;
    if(t / 2 && h[t / 2] > h[p]) p = t / 2;
    if(t != p)
    {
        heap_swap(t,p);
        up(p);
    }
}

int main()
{
    int n;
    cin >> n;
    int cnt = 0, m = 0;  // cnt为堆的总数,m为插入的数的下标
    while(n--)
    {
        string op;
        int k, x;
        cin>>op;
        if(op == "I")
        {
            scanf("%d", &x);
            cnt++;
            m++;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if(op == "PM") printf("%d\n", h[1]);
        else if(op == "DM")
        {
            heap_swap(1, cnt);
            cnt--;
            down(1,cnt);
        }
        else if(op == "D")
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k,cnt);
            cnt--;
            down(k,cnt);
            up(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            down(k,cnt);
            up(k);
            
        }
    }
    return 0;
}

哈希表

840.模拟散列表

哈希表需要开一个大小为质数且最接近数据范围大小的数组

查找质数方法

for(int i = 100000; ; i++){ //寻找100000后面的第一个质数
        bool flag = true;
        for(int j = 2; j * j <= i; j++){
            if(i % j == 0)
            {
                flag = false;
                break;
            }
        }
        if(flag)
        {
            cout << i << endl;
            break;
        }
    }
  • 开放寻址法

    • 开放寻址法一般就是要开数据范围2-3倍大小的数组

    • #include <iostream>
      #include <cstring>
      #include <algorithm>
      
      using namespace std;
      
      const int N = 200003;
      const int null = 0x3f3f3f3f; //规定空指针
      int h[N];
      
      int find(int x)
      {
          int k = (x % N + N) % N;
          while(h[k] != null && h[k] != x)
          {
              k++;
              if(k == N) k = 0;
          }
          return k;
      }
      
      int main()
      {
          int n, x;
          cin >> n;
          string op;
          
          
          memset(h, 0x3f, sizeof h);
          
          while (n -- )
          {
              cin >> op >> x;
              if(op == "I")
              {
                  int k = find(x);
                  h[k] = x;
              }
              else
              {
                  if(h[find(x)] == null)
                      puts("No");
                  else
                      puts("Yes");
              }
          }
          
          
          return 0;
      }
      
  • 拉链法

    • 拉链法就是要构造单链表

    • #include <iostream>
      #include <cstring>
      #include <algorithm>
      
      using namespace std;
      
      const int N = 200003;
      
      int h[N], e[N], ne[N], idx = 0; // 构造单链表
      
      void insert(int x)
      {
          int k = (x % N + N) % N;
          idx++;
          e[idx] = x;
          ne[idx] = h[k];
          h[k] = idx;
      }
      
      bool find(int x)
      {
          bool flag = false;
          int k = (x % N + N) % N;
          for(int i = h[k]; i != -1; i = ne[i])
          {
              if(e[i] == x)
              {
                  flag = true;
                  break;
              }
          }
          return flag;
      }
      
      int main()
      {
          int n, x;
          cin >> n;
          string op;
          memset(h, -1, sizeof h);
          while (n -- )
          {
              cin >> op >> x;
              if(op == "I")
              {
                  insert(x);
              }
              else
              {
                  if(find(x))
                      puts("Yes");
                  else
                      puts("No");
              }
          }
          
          return 0;
      }
      

841.字符串哈希

  • 字符串哈希和kmp算法近似,使用kmp算法的可以使用字符串哈希
  • 字符串哈希一般指前缀字符串哈希-----比较重要
    • 将字符串映射成p进制的数然后modQ 映射成[0, Q-1]的值
  • 注意:
    1. 不能映射成0
    2. p一般取131或者13331;Q=2^64时,可以有99.99%的把握认为没有冲突
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+5,P = 131;//131 13331
ULL h[N],p[N];

// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或  13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL query(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int main(){
    int n,m;
    cin>>n>>m;
    string x;
    cin>>x;

    //字符串从1开始编号,h[1]为前一个字符的哈希值
    p[0] = 1;
    h[0] = 0;
    for(int i=0;i<n;i++){
        p[i+1] = p[i]*P;            
        h[i+1] = h[i]*P +x[i];      //前缀和求整个字符串的哈希值
    }

    while(m--){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(query(l1,r1) == query(l2,r2)) printf("Yes\n");
        else printf("No\n");

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值