reference:
0.前言
本文志在梳理面试手撕和算法笔试中常考算法的模版题和基本实现代码,便于有需要的同学自信查阅复习,并会一定程度梳理一些进阶的内容,算是倒逼自己学习的一种方式,打牢算法基础,拿下理想offer!
1.简单库函数数据结构
1.1序列操作
就一些简单的增删改查,还是遇到了一些bug的,比如
- 从小到大排列的话用的是less<int>(),类似于可以想象为一个排列的小于号 1 < 2 < 3
- 从大到小就是greater<int>()
- vector的插入是插入到指定的位置,比如vector.begin(),就是插入到index = 0的位置,而不是插入到index = 0的后面
cpp
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;
int main() {
int ops;
cin >> ops;
vector<int> seq;
int x;
int size ;
for (int opIndex = 0; opIndex < ops; ++opIndex) {
int op;
cin >> op; //需要根据操作类型决定输入的参数
switch (op) { //正好很久没有写过switch case了
case 1:
cin >> x;
seq.emplace_back(x); //末尾添加
break;
case 2:
seq.pop_back();
break;
case 3:
int i;
size = seq.size();
cin >> i;
// for(auto a : seq){
// cout<<"sss";
// cout<<a<<' '<<i;
// }
if (i < size ) {
cout << seq[i] << endl;
} else {
cout << "Index out of scope" << endl;
}
break;
case 4:
int idx;
cin >> idx >> x;
seq.insert(seq.begin() + idx+1, x);
break;
case 5:
sort(seq.begin(), seq.end(), less<int>());
break;
case 6:
sort(seq.begin(), seq.end(), greater<int>());
break;
case 7:
size = seq.size();
cout << size << endl;
break;
case 8:
for (auto s : seq) {
cout << s << " ";
}
cout << endl;
break;
}
}
}
// 64 位输出请用 printf("%lld")
1.2 TODO:待补充
2.排序算法
2.1 快速排序
主要是快排,其他的用库函数就行,一般手撕都是让写快排就完事了。
原理
分区操作(Partition)
快速排序的关键步骤是分区操作。通常选择一个基准值(pivot),将数组分为两部分:
- 左子数组:所有元素小于或等于基准值。
- 右子数组:所有元素大于基准值。
分区操作的具体实现:
- 选择基准值(通常为数组的第一个元素、最后一个元素或随机元素)。
- 使用双指针(i和j)从数组起点开始扫描
- j向右移动,直到找到大于基准值的元素。
- 交换i和j指向的元素,i++,直到j指向最后一个元素
- 将基准值与相遇点的元素交换,完成分区。
递归排序
分区完成后,对左右子数组分别递归调用快速排序:
- 对左子数组(小于基准值的部分)进行快速排序。
- 对右子数组(大于基准值的部分)进行快速排序。
递归的终止条件是子数组的长度为0或1,此时数组已经有序。
代码
有几个需要注意的地方:
1.选择初始基准需要随机化,否则当数组有序的时候,每次选择都是待排序的最大,需要交换所有的元素,复杂度退化到O(n^2)
2.注意判断的条件得写成 numsj < povit,否则对于元素都相等的数组,复杂度退化到O(n^2)
cpp
#include <iostream>
#include <vector>
using namespace std;
//参考这个
int partition_v2(vector<int>& nums,int low,int high){
int i = low+rand()%(high-low+1);
swap(nums[high],nums[i]);//把基准值先放到最后
int povit = nums[high];
i = low; //1.注意开始的时候i设置为-1比较方便
for(int j = low; j < high; ++j){ //2.注意这里需要小于high因为我们选择的是high作为基准所以是不动的
if(nums[j] < povit){
swap(nums[i],nums[j]); // 这一步是把所有大于基准的元素交换到小的这一边来
i++;
}
}
swap(nums[i],nums[high]); // 基准元素归位
return i;
}
int partition(vector<int>& nums,int low,int high){
int i = low+rand()%(high-low+1);
swap(nums[high],nums[i]);
int povit = nums[high];
i = low-1; //1.注意开始的时候i设置为-1比较方便,设置为0也是可以的,不过要确定好i++和交换的顺序就行,以及最后返回基准值的位置
for(int j = low; j < high; ++j){ //2.注意这里需要小于high因为我们选择的是high作为基准所以是不动的
if(nums[j] < povit){
i++;
swap(nums[i],nums[j]); // 这一步是把所有大于基准的元素交换到小的这一边来
}
}
swap(nums[i+1],nums[high]); // 基准元素归位
return i+1;
}
void quick_sort(vector<int>& nums,int low,int high){
if(low < high){
int pos = partition(nums,low,high);
quick_sort(nums,low,pos-1);
quick_sort(nums,pos+1,high);
}
}
int main() {
srand(time(NULL));
int n;
cin>>n;
vector<int> nums;
for(int i = 0; i < n; ++i){
int a;
cin>>a;
nums.emplace_back(a);
}
quick_sort(nums,0,n-1);
for(int num : nums){
cout<<num<<' ';
}
}
// 64 位输出请用 printf("%lld")
练习题
3.数学算法
3.1判断素数
原理
原理很简单:所有大于3的素数均可表示为6k±1(如5=6×1-1,7=6×1+1),可自行判断 6k+2 3 4的时候必然是合数,6k+5本质就是6(k+1)-1,,综合一下就是6k±1
代码:
注意关键点:
1.注意函数的含义,是素数返回true,不是返回false
2.传入参数的范围,看清楚是int还是long long,范围不对可能因为数值溢出导致判断出错,牛客上的模版题目数字范围在1~10\^{12}
-
int范围:约-2.1×10⁹~2.1×10⁹(32位),超出会溢出,导致判断错误或死循环。 -
long long范围:约-9.2×10¹⁸~9.2×10¹⁸(64位),足以容纳题目常见的10^9或更大数值
3.注意循环起始数字是从5开始。可能会疑惑,明明我们只需要检查i-1和i+1就行,为什么不从i=6开始。因为num的平方根可能恰好等于i-1,这时候你从i试图进入循环,就会漏检这一个。比如829921=911×911,i从6开始倍增,到912,循环判断会直接跳出,不会去检查i-1 = 911是否是因数,所以要从最小的因子开始逐一检查,避免漏掉因子的情况
cpp
bool is_prime(long long num){
if(num < 2) return false;
if(num == 2 || num == 3) return true;
if(num % 2 == 0 || num % 3 == 0) return false;
for(long long i = 5; i*i <= num; i = i + 6){
if(num % (i) == 0 || num % (i+2) == 0){
return false;
}
}
return true;
}
练习题
TODO:待更新