蓝桥杯备赛——基础算法
算法评价机制
时间复杂度
1)时间复杂度是衡量算法执行时间随输入规模增长的增长率。
2)通过分析算法中基本操作的执行次数来确定时间复杂度。
3)常见的时间复杂度包括:常熟时间O(1)、线性时间O(n)、对数时间O(log n)、平方时间O(n2)等
4)在计算的时候我们关注的是复杂度的数量级。
一般来说我们关注的是最坏时间复杂度,用O(f(n))表示,大多数时候我们仅需估算即可。
一般来说,评测机1s大约可以跑2e8次运算,我们要尽可能地让我们的程序运算规模的数量级控制在1e8以内。
空间复杂度
1)空间复杂度是衡量算法执行过程中所需地存储空间随输入规模增长的增长率。
2)通过分析算法中所使用的额外存储空间的大小来确定空间复杂度。
3)常见的空间复杂度包括:常数空间O(1)、线性空间O(n)、对数空间O(log n)、平方空间O(n2)等。
一般我们关注的是最坏空间复杂度,用O(f(n))表示,大多数时候程序占用的空间一般可以根据开的数组大小精确算出,但也存在需要估算的情况。题目一般不会卡空间,一般是卡时间。
分析技巧
1)基本操作:基本操作可以是算术运算(加法、乘法、位运算等)、比较操作、赋值操作。这些操作我们称为原子操作O(1),如果某算法只执行这些操作n次就称该算法的时间复杂度是O(n),n2次就称时间复杂度是O(n2)例如两层for循环。
2)循环结构:循环是算法中常见的结构,它的执行次数对于时间复杂度的分析至关重要。
3)递归算法:递归算法的时间和空间复杂度分析相对复杂,需要确定递归的深度以及每个递归调用的时间和空间开销。对于递归算法最常用的方法是画一棵递归树(二叉树),如果树有n层则22
4)最坏情况分析:对于时间复杂度的分析,通常考虑最坏情况下的执行时间。要考虑输入数据使得算法执行时间达到最大值的情况。
5)善用结论:某些常见算法的时间和空间复杂度已经被广泛研究和证明。可以利用这些已知结果来分析算法的复杂度。
基础算法:
枚举
算法介绍:枚举是一种基本的算法思想,它通过穷举所有可能的情况来解决问题。它的基本思想是将问题的解空间中的每个可能的解都枚举出来,并进行验证和比较,找到满足问题条件的最优解或所有解。
解空间:解空间指的是问题的所有解构成的一个集合,解空间可以是一个范围内的所有数字或者满足某种条件的结构。
算法使用范围:枚举算法适用于问题规模很小、解空间可穷举的情况。它的优点是简单直观,不需要复杂的数学推导,易于实现。但是对于问题规模较大的情况,美剧算法的时间复杂度可能会非常高,效率较低。
循环枚举解空间:
1.首先要确定解空间的维度,即问题需要枚举的变量个数。
2.对于灭个变量,确定其可能的取值范围。
3.在循环体内,震度每个可能解进行处理。可以进行问题的验证、计算、输出等操作。
例题:
分析题目:最初的序列是由n个正整数构成的序列,从中随机删除一个元素,然后求剩余子串中的最长上升子串。
输入格式:第一行是一个正整数n,表示给出的序列长度。
第二行是n个正整数,这个序列中间会用空格隔开。
输出格式:输出只包含一个正整数,就是删除了一个元素后的最长上升子串长度。
暴力枚举写法:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int MaxSubstringLength(int arr[], int len, int num){
int length = 1;
int max_length = 1;
//因为最长子串可以是任意起点,所以遍历整个序列,每次循环都选择序列中的一个元素作为起点。
for(int i=0; i<len; i++){
//length = 1;
//从起点开始搜索之后有没有连续上升子串,获取长度并与当前最长上升子串的长度进行对比。
if(arr[i]<arr[i+1]){
length += 1;
if(length > max_length){
max_length = length;
}
}
else{
length = 1;
}
}
return max_length;
}
int main(){
int arr[100000];//这个数组用来存储原序列,因为n的取值范围是[1,10^5],所以这里按最大可能情况考虑。
int arr2[99999];//用来装删除一个元素后的序列
int n;
int length = 1;
int max_length = 1;
cin >> n;
for(int i=0;i<n;i++){
cin >> arr[i];
}
//使用枚举,即设置一个循环,每次从序列中删除一个元素,然后把剩余的序列送入一个函数,此函数能够得到输入序列的最大子串。
for(int i=0;i<n;i++){
for(int j = 0; j<i;j++){
arr2[j] = arr[j];
}
for(int j=i;j<n-1;j++){
arr2[j] = arr[j+1];
}
length = MaxSubstringLength(arr2,n-1,i);
if(length > max_length){
max_length = length;
}
}
cout << max_length;
return 0;
}
但很遗憾,最大测试用例会超时通不过。
数据预处理:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
int a[100000],f[100000],g[100000];
int length = 1;
int max_length = 1;
//先获取所有的输入
cin >> n;
for(int i=0; i<n; i++){
cin >> a[i];
}
//从a中找出所有的上升区间,并把所有的上升区间从1开始每次递增1标号,例如a的1,5,7,100,81,101这几个数字在f中对应的就是1,2,3,4,1,2。
f[0] = 1;
for(int i=1; i<n; i++){
if(a[i] > a[i-1]){
f[i] = f[i-1] + 1;
}
else{
f[i] = 1;
}
}
//从a中找出所有的下降区间,并把所有的下降区间从1开始每次递增1倒序标号,例如a的1,5,7,100,81,101这几个数字在g中对应的就是4,3,2,1,2,1
g[n-1]=1;
for(int i=n-2; i>=0; i--){
if(a[i] < a[i+1]){
g[i] = g[i+1] + 1;
}
else{
g[i] = 1;
}
}
//此时我们再看a的1,5,7,100,81,101这几个数字,如果我们把81删去,那么剩下的几个数字就是一个连续的上升子串满足题意,所以我们该怎样从f和g中得到这个上升子串的长度?其实就是81前一个元素在f中对应的值加上81后一个元素在g中对应的值。
//但是上述想法是怎么得来的呢?此时我们从题目倒推,题目说是删去一个元素,从剩下的所有元素中找连续上升的子串并获取长度
//我们尝试想一下序列的情况:
//1.这个序列是一个递减序列,无论怎么删都是递减,所以最长上升子串就是1
//2.这个序列中每个上升区间与下一个上升区间不连续,例如:100,101,102,1,99,100,101这段数字中有两个上升区间:100,101,102和99,100,101它们被中间的数字1隔开了,那么即使我们删除了中间的1这两个子串也不能构成更长的上升字串,那么这种情况就直接比较所有单个上升子串长度即可。
//3.这个序列中每个上升区间与下一个上升区间连续,例如:100,101,102,1,103,104,105这段数字中有两个上升区间且删去中间的数字1之后这两个上升区间可以合并成一个更大的上升区间。
//4.这个序列中每个上升区间与下一个上升区间连续但中间隔了2个及以上数字,例如:100,101,102,1,1,103,104,105它们中间有两个上升区间但中间隔了两个不连续上升的数字,删去其中一个也不能把两个上升区间合并成更长的上升区间。
for(int i=0; i<n; i++){
if(!i){
length = max(length,g[i+1]);
}
else if(i == n-1){
length = max(length, f[i-1]);
}
else if(a[i-1] < a[i+1]){
length = max(length, g[i+1]+f[i-1]);
}
else{
length = max(length, max(g[i+1], f[i-1]));
}
}
cout << length;
return 0;
}
OK成功通过。