2022年3月17日 下午 作为一个打了8000把英雄联盟的帅哥,总结一下至今的一些刷题经验
以此迎接晚上的网易笔试
这是一种考虑局部最优的算法,一般来说是在遍历过程来选取当前最优的操作。贪心算法可能得到全局最优,这要求其局部最优的结果互相不干扰。
case:一般在解题过程中需要对序列进行按某种条件排序,之后遍历序列选择当前最优的操作。
其中经常出现的区间贪心,按左或右端点排序后,之后进行贪心选择。
1、两个指针在序列中维护一个区间,也称滑动窗口。
2、快慢指针,一般用在链表中
二分查找适用于有序结构,出来线性有序的数组,也可以用于二叉树查找树。
C++提供了两个标准库函数来支持二分查找
lower_bound(v.begin(),v.end(),val) 用来查找大于等于当前的元素的第一个位置
upper_bound() 用来查找大于当前元素的第一个位置
二分查找比较麻烦的是注意下标!
C++提供了sort()来进行快速排序
但有的时候,我们需要使用的是排序的思想。如使用快排来找到第k个大的数。
当我们需要解决一个问题的时候,我们需要完成解决问题的几个步骤,把这些步骤看成节点,其实我们就是在进行搜索了,搜索出可以解决问题的路径。
搜索有深度优先搜索和广度优先搜索两种:
一般使用于图结构,树形结构
深度优先搜索利用递归来实现,广度优先搜索利用队列来实现,需要在搜索的过程中判断是否满足问题解决条件以及记录搜索过程的状态。
深度优先搜索时还可以利用回溯来得到所有可能路径
如果一个问题可以分解为很多子问题,而在解决问题的过程中,一些子问题被重复计算,则可以先把之前解决的问题的状态存下来,通过状态转移来得到新的状态。
动态规划一般分为一维、二维,需要定义对应的dp数组,dp[i],dP[i][j]表示某种定义的状态,这里的i,j一般跟题目中的序列有关。动态规划的要点是定义当前状态怎么由之前的状态转移得到,还有一个是如何初始化最初状态。
经典问题有 背包问题 以及最长公共子序列问题
解决一个大的问题,可以把它拆分为子问题,化繁为简。递归的思想是把一个大问题化为一个很小的子问题以及一个较大的问题,又来处理这个较大的问题。分治则是把一个很大的问题分解为很多小问题。解决完小问题之后,再合并这些小问题。
经典的分治问题,归并排序
数学是一生的痛,高考数学拉跨,考了全班倒数,勉强超一本线70多分,低了全班平均分60多分。但凡考个全班平均分也能去985了。考研数学也没发挥好,考了个104,不然总分400多的话,复试状态再不好也不会被刷。不过本少还是充满希望,人生逆风翻盘不是梦!
最小公倍数和最大公约数
对于两个数,A和B, A*B = 两数的最小公倍数 * 两数的最大公约数
辗转相除法求最大公因数(每次用大的除于小的,余数为0时,大的那个就是最大公因数)
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
int lcm(int a, int b){
return a * b / gcd(a, b);
}
质数,这里使用埃氏筛选法来标记,一般只是适合1e6的数据范围内
vector<bool> vis(n, false);
vector[1] = true;
for(int i = 2; i < n; ++i){
if(!vis[i]){
for(int j = i + i; j < n; j += i){
vis[j] = true;
}
}
}
随机数,可以使用rand()函数来得到随机数。
与、或、非(取反)、异或
一般需要利用二进制位运算的一些特性
这里介绍一个快速幂
//求a的b次方,一般会使用double ans = pow(a,b)
int getPow(int a, int b){
int ans = 1;
int base = a;
while(b){
if(b&1) ans *= base;
b >>= 1;
base *= base;
}
return ans;
}
C++的sting封装了C风格的字符串数组。
字符串相等于一个字符序列,在遍历序列的过程中求解问题
这里题一下字符串的经典问题,字符串匹配问题
介绍一下序列自动机,字符串hash以及kmp算法
#include
using namespace std;
//序列自动机Next[i][26],Next[i][j]表示原串s中下标位置i后(包括i)第一个j出现的位置, j表示a-z
//用来判断一个序列是不是一个串的子序列,也可以用来判断一个串是不是另一个串的子串
//构建一个串的序列自动机
vector<vector<int>> getNext(string & s){
int n = s.size();
cout << "开始生成自动机!" << endl;
//这里下标从1开始,没有的话就默认为0
vector<vector<int>> Next(s.size() + 2,vector<int>(26, 0));
for(int i = n; i>= 1; --i){
for(int j = 0; j < 26; ++j){
Next[i][j] = Next[i + 1][j];
}
//更新当前字母出现的位置
Next[i][s[i - 1] - 'a'] = i;
}
cout << "生成自动机成功!" << endl;
return Next;
}
int main(){
string s1 = "adddffghhhjjjjlk";
//string s2 = "adfgh";
string s2 = "adddff";
vector<vector<int>> Next = getNext(s1);
/*int now = 1;
cout << s2.size() << endl;
for(int i = 0; i < s2.size(); ++i){
now = Next[now][s2[i] - 'a'];
cout << s2[i] << "出现在s1的位置处" << now << endl;
if(now == 0) {
cout << "s2不是s1的子序列!" << endl;
break;
}
now++;
}
if(now != 0) cout << "s2是s1的子序列!" << endl; */
//判断子串的话,下标出现的位置要连续
int now = 1, pre_now = 0;
cout << s2.size() << endl;
for(int i = 0; i < s2.size(); ++i){
now = Next[now ][s2[i] - 'a'];
cout << s2[i] << "出现在s1的位置处" << now << endl;
if(now == 0 || pre_now + 1 != now) {
cout << "s2不是s1的子串!" << endl;
break;
}
pre_now = now;
now ++;
}
if(now != 0) cout << "s2是s1的子串!" << endl;
return 0;
}
字符串hash
#include
using namespace std;
//字符串哈希
//hash[i]存放串1~i对应的哈希值
//所以l~r对应部分串的哈希值为hash[r] - hash[l - 1]*p^(r - l + 1);
//由于数值可能较大
//开unsign long long给hash,base的话取2333;
//获取串s的字符串hash
const unsigned long long base = 23333;
vector<unsigned long long> getHash(const string& s){
int n = s.size();
vector<unsigned long long> hash(n + 1,0);
for(int i = 1; i <= n; ++i){
hash[i] = hash[i - 1] * base + s[i] - 'a' + 1;
}
return hash;
}
//存储base的n次方,方便求l-r的串的哈希值
vector<unsigned long long> getPow(const string s, const unsigned long long base){
int n = s.size();
vector<unsigned long long> pow(n + 1, 1);
for(int i = 1; i <= n; ++i){
pow[i] = pow[i - 1] * base;
}
return pow;
}
unsigned long long get_son_hash(int l, int r, vector<unsigned long long> hash, vector<unsigned long long> pow){
l = l + 1;
r = r + 1;
return hash[r] - hash[l - 1] * pow[r - l + 1];
}
int main(){
string s = "asdasd";
vector<unsigned long long> hash = getHash(s);
vector<unsigned long long> pow = getPow(s, base);
cout << get_son_hash(0,2, hash, pow) << endl;
cout << get_son_hash(3,5, hash, pow) << endl;
cout << (get_son_hash(0,2, hash, pow) == get_son_hash(3,5, hash, pow))<< endl;
return 0;
}
kmp算法匹配
这里要注意啊兄弟们,.size()返回来的是无符号数,但kmp里j可能为-1,千万不要拿无符号数和有符号数比较。
#include
using namespace std;
//kmp算法,用来匹配主串和子串
//next[j] = k,表示在0~j中从0~k-1的部分与j-k ~ j-1的部分相同
vector<int> getNext(string t){
vector<int> next(t.size(), 0);
int k = -1, j = 0;
next[j] = k;
int m = t.size();
//j
while(j < m - 1){
if(k == -1 || t[k] == t[j])
{
next[++j] == ++k;
}
else{
k = next[k];
}
}
return next;
}
int main(){
string s;
string t;
while(cin >> s >> t){
vector<int> next = getNext(t);
int i = 0, j = 0;
int n = s.size(), m = t.size();
while(i < n && j < m){
if(j == -1 || s[i] == t[j]){
i++;
j++;
/*如果要判断多次匹配的话
if(j + 1 == m){
//ans为多次匹配的次数
ans++;
j = next[j];
; }
else{
++i;
++j;
}
*/
}
else j = next[j];
}
if(j == m) cout << "匹配成功!" << endl;
else cout << "匹配不成功!" << endl;
}
return 0;
}
STL提供了顺序容器、关联容器
一般数据结构分为线性结构:顺序表、栈、队列
非线性结构:树、图、并查集
数据结构最好熟悉基本的算法
这里写一个并查集的的代码,用来判断两个点是否连通
vector<int> f(n+1,0);
//初始话的适合f[i] = i;每个节点作为一个分支的根
for( int i = 1; i <= n; ++i) f[i] = i;
int _find(int x){
if(x == f[x]) return x;
return f[x] = _find(f[x]);
}
排序算法总结与实现快排、归并排序、堆排序
#include
using namespace std;
//排序算法学习 快排,归并排序,堆排序
//1、快排:是一种交换排序,每次选择一个基准,小于基准的元素放左边,大于基准的元素放右边
//每次可以确定一个基准在有序序列中的最终位置
void quicksort(vector<int>&nums, int left, int right){
if(left < right){
//选取基准
int temp = nums[left];
int i = left;
int j = right;
while(i < j){
while(i < j && nums[j] >= temp) --j;//从后往前找比基准小的
nums[i] = nums[j];
while(i < j && nums[i] <= temp) ++i;//从前往后找比基准大的
nums[j] = nums[i];
}
nums[i] = temp;//确定了当前基准的最终位置
//递归的排序基准左边的元素
quicksort(nums, left, i - 1);
//递归的排序基准右边的元素
quicksort(nums, i + 1, right);
}
}
//2、归并排序:分治与递归的结合,这里使用两路归并排序,先归并排序好子序列,再合并排序好的子序列
void mergesort(vector<int> &nums, int left ,int right){
//递归出口
if(left < right){
int mid = (left + right) / 2;
mergesort(nums, left, mid);
mergesort(nums, mid+1, right);
//前面把[left,mid]和[mid+1,right]排好有序了
//现在来合并它们
vector<int> copy(nums);
int i = left, j = mid + 1;
int pos = left;
while(i <= mid && j <= right){
if(copy[i] < copy[j]){
nums[pos++] = copy[i];
++i;
}
else{
nums[pos++] = copy[j];
++j;
}
}
while(i <= mid){
nums[pos++] = copy[i++];
}
while(j <= right){
nums[pos++] = copy[j++];
}
}
}
//3、堆排序,堆排序是选择排序的一种,大根堆的定义是对于序列A[N], A[2i] < A[i]且A[2i+1] < A[i]
//堆排序的前提是把序列转换为堆,堆维持其数据结构特性有两种操作,sink下沉(删除的时候使用) swim上浮 (插入的时候使用)
//对一个顺序表维护一个大顶堆,开始s,结束e满足大顶堆
void heapkeep(vector<int>&nums, int s, int e){
int temp = nums[s];
//注意这里我们的下标为0,所以要加一
for(int j = 2 * s + 1; j <= e; j *= 2){
if(j < e && nums[j] < nums[j + 1]) ++j;//选择子节点最大的
if(temp > nums[j]) break;
nums[s] = nums[j];
s = j;
}
nums[s] = temp;
}
void heapsort(vector<int> &nums){
//把待排序序列维护为一个堆
for(int i = nums.size() / 2 ; i >= 0; --i){
heapkeep(nums, i, nums.size());
}
//每次选择前面一个元素再与后面的交换,再维护前面为堆
for(int j = nums.size() - 1; j >= 0; --j){
swap(nums[0], nums[j]);
heapkeep(nums,0,j-1);
}
}
int main(){
vector<int> nums = {1,3,2,3,5,7,8,9,0};
//quicksort(nums, 0, nums.size() - 1);
//mergesort(nums, 0, nums.size() - 1);
heapsort(nums);
for(auto v : nums) cout << v << " " ;
cout << endl;
return 0;
}
LRU缓存,c++实现
#include
using namespace std;
class LRUCache{
unordered_map<int, list<pair<int,int>>::iterator> hash;//用来查找的
list<pair<int,int>> cache;// 用list来作为cache存储容器
int size;//当前cache的容量
public:
LRUCache(int capacity):size(capacity){
}
int get(int key){
auto it = hash.find(key);//若找到返回hash的key的迭代器,否则返回尾后迭代器end()
if(it == hash.end()) return -1;//没有找到,返回-1
int ans = it->second->second;
cache.splice(cache.begin(), cache, it->second);//调用list的splice函数将当前迭代器换到list首部
return ans;
}
void put(int key, int value){
auto it = hash.find(key);
if(it != hash.end()){
it->second->second = value;
cache.splice(cache.begin(), cache, it->second);//把最近操作的节点放到首部
return;
}
cache.insert(cache.begin(), make_pair(key,value));
hash[key] = cache.begin();
if(cache.size() > size){
hash.erase(cache.back().first);
cache.pop_back();
}
}
};
int main(){
LRUCache my_cache(5);
my_cache.put(1,5);
my_cache.put(2,3);
cout << my_cache.get(1) << endl;
return 0;
}
图论算法复习,DFS,BFS,最短路径算法,最小生成树算法,拓扑排序
DFS,全排列
#include
using namespace std;
//dfs问题,深搜暴力
class DFS {
public:
void dfs(vector<vector<int>> &ans, vector<int> now, vector<int> &nums, deque<bool> &vis){
if(now.size() == nums.size()){
ans.push_back(now);
return;
}
for(int i = 0; i < nums.size(); ++i){
if(vis[i]) continue;
now.push_back(nums[i]);
vis[i] = true;
dfs(ans, now, nums, vis);
vis[i] = false;
now.pop_back();
}
}
vector<vector<int>> permute(vector<int>& nums) {
//dfs 返回全排列
vector<int> now;
vector<vector<int>> ans;
deque<bool> vis(nums.size(), false);
dfs(ans, now, nums, vis);
return ans;
}
void display(vector<vector<int>> ans){
for(int i = 0; i < ans.size(); ++i){
for(auto v : ans[i]){
cout << v << " ";
}
cout << endl;
}
}
};
int main(){
DFS my_dfs;
vector<int> nums = {1,2,3,4,5};
vector<vector<int>> ans = my_dfs.permute(nums);
my_dfs.display(ans);
return 0;
}