算法题目总结:抓突破点的本质-解决问题

本文总结了算法解题的关键知识点,包括基础算法如二分、前缀和、双指针、位运算,数据结构如链表、栈、队列、Trie树,以及动态规划、贪心策略、搜索与图论等。此外,还介绍了C++做题技巧和算法复杂度分析,帮助提升解题能力。
摘要由CSDN通过智能技术生成

学习目的:生存的更好/卷得开心,改造环境

 将设计变现:算法逻辑实现,这就是编程的美妙之处(模板+输入输出的随机应变处理)
 学习方法:

画图,思考,应用————>提前准备好模板和应对措施就会大大提高效率(熟练掌握,能够非常快地把代码默写出来)
做题:
	1. 一定要看懂题目的前提下再做题:抓住突破点!!
	2. 先写出暴力思路,然后优化思路和实现
	3. 分解问题,解决问题

在这里插入图片描述


基础算法

二分

  1. 性质:有单调性的话一定可以二分,但是二分的题目不一定必须要求单调性
  2. 整数二分的本质:边界,整个区间一分为二,左满足右不满足,二分可以寻找满足性质的边界
    在这里插入图片描述

两种不会错的万能板子:(整数二分)
在这里插入图片描述

如何选择模板:

  1. 先写check函数
  2. 思考true和false如何更新

二分的主要思想:答案所在的区间(保证区间里面有答案)

  1. 浮点数二分:
    输出r和l均可,因为r和l足够接近
    在这里插入图片描述

前缀和与差分

一维前缀和:
Si = a1+a2+a3+…+ai

  1. 如何求Si:Si-1得到
  2. 求sum[l, r]:Sr-Sl-1

二维前缀和:
在这里插入图片描述
在这里插入图片描述

差分:

  1. 用处:构造差分数组,使得b数组是a数组的差分,对b数组求一遍前缀和就可以求出来原数组(O(n)),能够使得在O(1)操作a数组中某连续区间增减固定数值
  2. al-ar的区间加c:bl+c,br+1 - c

双指针

统一模板:最核心性质是将朴素的O(n^2)算法优化到O(n)
应用:输出单词,快排,KMP
思路:先思考暴力怎么写,然后看出i和j之间的单调关系,有单调关系的话利用ij之间的单调关系把时间复杂度由n^2转变成n
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

应用场景:

  • 根据双指针唯一化数组:满足性质(a[i-1]!=a[i]),则a[j++]=a[i];

位运算操作

数字n的二进制中1的个数:n>>K&1

  1. 先把第k位移到最后一位n>>k
  2. 看个位是0 or 1:x&1

lowbit操作:返回x的最后一位1
x=1010,返回:10
x=101000, 返回:1000
表达式:x&-x(复数是取反加一)

区间合并算法:贪心(端点排序)

具体应用场景:快速地把有交集的区间进行合并

  1. 首先根据区间左端点排序
  2. 扫描,维护一个区间[st, ed],根据下一个和当前右端点的交集关系判断
离散化:值域跨度很大,但是个数非常稀疏
  1. 首先用vector对数组排序归一化(注意,已知下标数据和询问下标数据都要记录,进行离散化)
  2. 离散化
    在这里插入图片描述

高精度

加法,减法

设置vector,根据设置进位t,实时记录借位

乘法:大数*小数

本位:取模10
进位:除以10的数

排序算法

//基于比较的二分思想的排序算法

快速排序:分治 O(nlogn)

解决方案:确定分界点,确定左右区间(划分)
优美方法:双指针处理算法,循环迭代每次交换
背住板子,注意进入子函数后的边界问题和选择的点,避免死循环

在这里插入图片描述

//应用:第k个数
解决方案:快排的处理操作,单次递归
快排的每一趟,数轴的左边都会是 <= x 的, 右边都是 >= x 的。
左边元素的个数是 s1 = j - l + 1, 如果k <= s1 的话,那么下次递归的区间就是左边,否则右边。
直到 l == r 时返回q[l]。

在这里插入图片描述

归并排序: O(nlogn)

解决方案:以数字中间点为分界点(左右两端的二分点)划分左右区间,递归排序操作;归并操作合二为一

//应用:
逆序对的数量
解决方案:

  1. 根据中间的划分边界点利用分治思想将逆序对分成三大类,
  2. 为什么能用归并板子做逆序对题目:在归并两个数组中正好根据元素所属可求出原数组逆序对数目

数据结构

//线性结构:
用数组模拟

效率:动态链表方式效率较低
链表

数组模拟单链表:

最多的是邻接表(n个链表):存储树和图

在这里插入图片描述
在这里插入图片描述

数组模拟双链表:

用来优化某些问题:每个节点有两个指针,一个指向前,一个指向后

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

数组模拟:
	栈:先进后出
	队列:先进先出

在这里插入图片描述

//应用:
中缀表达式求值(中缀表达式树:遍历),运算顺序用栈来做
解决方案:

定义不同栈存储操作符和数字
处理初始表达式,进入不同栈,根据当前运算符和栈顶的元素选择是否操作

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

// 单调栈:
常见模型:给一个序列,求这个序列中某数离其左边/右边最近的最大/最小的数
解决方案:利用栈进行处理,根据单调要求,存储的是严格上升的数
在这里插入图片描述

队列

在这里插入图片描述

// 单调队列:滑动窗口
常见模型:求数组中滑动队列中数的最小值
解决方案:

  1. 普通队列
  2. 删除无用元素,使得队列具备单调性
  3. 直接从模拟数组的队头/队尾O(1)取得最值在这里插入图片描述

在这里插入图片描述

字符串
KMP

失配时:
朴素做法:O(n*m),后移一位
KMP处理:根据预处理已匹配过的前后缀长度,利用后移数值增加 O(n)

在这里插入图片描述

Trie树(串树:字母类型不会很多 )

应用:高效地存储和查找字符串集合的数据结构
问题模型:统计字符串出现频率
解决方案:

从根节点开始存储每个节点 ,每个叶节点打标记得到该字符串
根据字符串的内容创建路径/树的分支,即得串树

代码实现思路:数组模拟插入查询(多叉树)

在这里插入图片描述

在这里插入图片描述

//应用:最大异或对
问题模型:给定n个整数,从其中挑出两个整数,求最大异或值
解决方案:

优化暴力:
1. 首先将整数构造为01串,然后根据01字符串构造Trie树
2. 枚举每个数,根据Trie树查询到针对当前枚举的数找到的最大数

在这里插入图片描述

数组模拟手写实现:建堆,创建,删除
维护一个数据集合:up和down操作
1. 插入一个数
2. 求集合当中的最小值
3. 删除最小值
4. 删除任意一个元素
5. 修改任意一个元素

在这里插入图片描述

建堆:利用down操作的时间复杂度为O(n)

在这里插入图片描述

在这里插入图片描述

//应用-模拟堆:增加操作删去第k个元素

解决方案:
1. 增加双向映射hp(堆指向映射),ph(映射指向堆节点)
2. 增加外部映射数组,交换堆中两节点的同时交换

在这里插入图片描述
在这里插入图片描述

	首先交换蓝颜色两条边
	之后交换绿色指向边
	最后交换节点数值
并查集:代码精简,思路精巧(思维性)

应用:快速处理集合,近乎O(1)

1. 将两个集合合并
2. 询问两个元素是否在一个集合当中

基本思想:

树的形式维护所有集合:每个集合用一颗树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点
问题1:如何判断树根:if(p[x] = x)
问题2:如何求x的集合编号:只要x不是树根编号,就一直往上走  while(p[x] != x) x = p[x];————>优化:路径上所有的节点都直接指向根节点,一遍即可(很好的加速-路径压缩)
问题3:如何合并两个集合:px,py分别是x,y的集合编号,让p[x]=y;

在这里插入图片描述

应用:图中连通块中点的数量

在这里插入图片描述

解决方案:集合维护连通块,维护一个size即可

在这里插入图片描述

应用:食物链

解决方案:判断构成环形
在这里插入图片描述

	并查集维护额外信息:表示同类和被吃的关系
	如何确定关系:记录每个点和根节点之间的关系,就可以知道任意两点之间的关系(判断领袖与节点的关系)
			点到根节点的距离表示关系:余1吃根节点,余2被根节点吃,余0与根节点同类
			从根节点到不同点的距离可来分析这些点的关系:不同点之间的d值模3同余的是同类

代码实现:p存储father,d存储点到根节点之间的距离(在find过程中处理)

	初始化集合
	处理关系:根据关系确定新节点的d值,处理的突破点是根据模余关系得到吃与被吃的关系

在这里插入图片描述

#include<iostream>
#include<vector>

using namespace std;
const int N = 100010;
int p[N],d[N];

int find(int x){
   
    if(p[x] != x){
   
        int t = find(p[x]);
        d[x] += d[p[x]];   //回溯到根,在递归结束的栈顶依次处理节点到根的距离
        p[x] = t;    //节点关系路径压缩
    }
    return p[x];
}
int main(){
   
    int ans = 0;
    int n,m;
    scanf("%d%d",&n,&m);

    for(int i = 1;i <= n;i++)p[i] = i;   //初始化

    while(m--){
   
        int z,x,y;
        scanf("%d%d%d",&z,&x,&y);

        //取出x,y的两个根节点
        int px = find(x),py = find(y);

        if(x > n || y > n)ans++;
        else if(z == 1){
   
            //也就是说默认出现的第一个x,y都是默认正确的,只是寻找后面的x,y是否符合前面x,y的规范
            //注意只有在同一颗树的中,才能进行操作,否则将其进行加入
            //查看两个节点的距离是否模除为0 不要进行d[x] % 3 == d[y] % 3的操作 因为有可能出现负数
            if(px == py && (d[x] - d[y]) % 3 )ans++;  //在一个集合中不是同类
            else if(px != py){
    //不在一个集合中,进行合并操作
                p[px] = py;
                d[px] = d[y] - d[x];
            }
        }
        else{
   
        //注意这里的d[x] - d[y] + 1 模3 判断,那么下面的将两个集合放在一起的操作就需要相应的减一
            if(px == py && (d[x] 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值