综述:
💞目的:本系列是个人整理为了
秋招算法
的,整理期间苛求每个知识点,平衡理解简易度与深入程度。🥰来源:材料主要源于
网上知识点
进行的,每个代码参考热门博客和GPT3.5,其中也可能含有一些的个人思考。🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
文章目录
编码平台格式
ACM模式
-
注意事项
使用long代替int
:标准规定int 至少 16 位,long int 至少 32 位,并且 sizeof(int) <= sizeof(long),所以在不同的编译器下,int可能位数不足出现整形溢出问题。- 奇数判断
(n & 1) == 1
:因为奇数的二进制尾数为1,二进制速度快。
-
基础输入要点
- 引用库需要自己加上对应的库,如
#include <algorithm>
- 输入使用
while (cin >> a ){ 算法主体 }
- 输出使用
cout
,注意删除自己的测试输出,不能使用return
,否则会一直报错语法错误
- 输入示例:
cpp#include <vector> #include <iostream> using namespqce std; int main() { long n = 0; // 表示n轮输入 cin >> n; while (n--) { int c = 0; // 每轮输入的整数个数 cin >> c; vector<long> vec(c, 0); for (int i = 0; i < vec.size(); ++i) cin >> vec[i]; }
- 引用库需要自己加上对应的库,如
-
二维数组的初始化
cppvector<vector<int>> dp(rows, vector<int>(cols, 0));// 注意要进行初始化 for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { cin >> dp[i][j];// 不能使用push_back()进行处理 }
-
输入一行以回车结尾的数字
cppvector<int> vec; int tmp = 0; do {// 不能使用while(){},因为会丢失第一个输入 cin >> tmp; vec.push_back(tmp); } while (cin.get() != '\n');
笔试基础
基本代码范式
-
基本逻辑范式
cppbool function(){ // 1.健壮性检查 if (函数形参不符合情况) { doing(); return false; } // 2.初始化:给工作变量赋初值,符合要求的第一次循环条件 int initial_value = 0;// 会被算法初始化的也应该赋初值 // 4.算法逻辑 while (工作变量符合算法循环条件) {// 注意考虑最后不足算法增量的部分 doing();// 对结果序列操作的函数 工作变量的迭代;// 注意工作变量在使用完成后已经被污染 } // 5.收尾 处理不足最后一次算法增量的部分 return true; }
-
递归逻辑范式
cppvoid Recursion(vector<int> &vec,...){ // 递归出口 if (结束条件) return ; // 递归体 Doing(); }
基本算法框架
-
快慢指针
- 作用:可用于线性结构的条件遍历处理,如链表、数组等
- 优点:可以将
两次循环
降维成条件筛选+一次循环
cpp// 示例:删除数组中的元素 int RemoveElement(vector<int>& nums, int val) { // 健壮性检查 if (nums.empty()) return -1; // 初始化操作 int slow = 0; // 慢指针负责更新处理 int fast = slow; // 快指针负责拓展选择 // 算法部分 while(fast < nums.size()){ if(nums[fast] != val){ // 快指针负责条件判断 nums[slow] = nums[fast]; ++slow; ++fast; } ++fast; } return slow; } // 示例:环形链表的入口
-
滑动窗口
- 右边界指针负责拓展,左边界指针负责收缩
cppvoid SlideWindow(vector<int> vec) { // 功能函数部分 auto slide_windows = [](vector<int> &nums, int left, int right){ // 直到到大窗口的右边界 // 直到到达窗口右边界停止 while(right < nums.size()) { // - 扩大右边界并更新窗口状态 ... right++; // - 窗口到达什么状态需要收缩 while(需要收缩) { // - 缩小左边界并更新窗口状态 ... left++; } } }; // 代码逻辑部分 // 健壮性处理 if (nums.size() <= 1) return ; // 初始化 int left = 0; int right = 0; // 算法部分 slide_windows(vec, left, right); }
-
二叉树遍历算法
- 广度优先遍历
- 深度优先遍历
cpp// 二叉树的基本数据结构 struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int v) : val(v), left(nullptr), right(nullptr){} }; // 深度优先的递归遍历 // 中序遍历 void Traversal(TreeNode *root) { if (root == nullptr) return ; Traversal(root->left); // 左 Doing(root->val); // 中 Traversal(root->right); // 右 } // 深度优先的非递归遍历 vector<int> Traversal(TreeNode* root) { // 初始化 vector<int> result; // 结果容器 stack<TreeNode*> st; // 深度的栈 if (root != NULL) // 根非空则入栈 st.push(root); // 遍历源容器 while (!st.empty()) { TreeNode* node = st.top(); // if (node != NULL) { st.pop(); // 算法变化的部分,遍历的逆序 // 中 st.push(node); st.push(NULL); // 右 if (node->right) st.push(node->right); // 左 if (node->left) st.push(node->left); } else { // 对值节点的处理 st.pop();// 弹出空值结点 node = st.top(); st.pop(); // 结点处理 result.push_back(node->val); } } return result; } // 广度优先的非递归遍历 vector<vector<int>> levelOrder(TreeNode* root) { // 初始化 vector<vector<int>> result; // 结果容器 queue<TreeNode*> que; // 广度的队列 if(root != nullptr) // 根非空则入列 que.push(root); // 算法 while (!que.empty()) { // 队列非空 vector<int> vec; // 结果存放 TreeNode* node; // 过程记录 int size = que.size(); // 初始化:记录每层要遍历的根节点数量 for (int i = 0; i < size; i++) { // que.size()会变化 // 处理结点 node = que.front(); // 记录队首结点 que.pop(); // 弹出队首结点 if (node->left) que.push(node->left); if (node->right) que.push(node->right); // doing:处理结点 vec.push_back(node->val); } // 将每层筛选元素压入结果数组中 result.push_back(vec); } // 输出 return result; }
-
回溯算法
- 组合问题
- 有重复元素的组合
- 无重复元素的组合
- 排列问题
- 有重复元素的全排列
- 无重复元素的全排列
- 组合问题
cpp
// 组合问题
// 无重复元素的组合
class Solution {
private:
// 回溯核心算法
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果
void BackTracking(vector<int> &vec, int start, int target) {
// 递归出口:满足条件则加入结果集中
if (path.size() == target) {
result.push_back(path);
return ;
}
// 回溯算法
for (int i = start; i < vec.size(); i++) {
// 剪枝条件
if (i > vec.size() - (target-path.size()))
continue;
path.push_back(vec[i]); // 做出选择
BackTracking(vec, i + 1, target);// 递归
path.pop_back(); // 撤销选择
}
}
public:
vector<vector<int>> combine(vector<int>vec, int k) {
result.clear(); // 可以不写
path.clear(); // 可以不写
BackTracking(vec, 0, k);
return result;
}
};
// 有重复元素的组合
class Solution {
public:
vector<vector<int>> combine(vector<int> vec, int k) {
result.clear(); // 可以不写
path.clear(); // 可以不写
sort(vec.begin(), vec.end());
BackTracking(vec, 0, k);
return result;
}
};
private:
// 回溯核心算法
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果
void BackTracking(vector<int> &vec, int start, int target) {
// 递归出口:满足条件则加入结果集中
if (path.size() == target) {
result.push_back(path);
return ;
}
// 回溯算法
for (int i = start; i < vec.size(); i++) {
// 剪枝:重复选择只选一次,需要配合sort使用
if (i > start && vec[i] == vec[i - 1])
continue;
// 回溯步骤
path.push_back(vec[i]); // 做出选择
BackTracking(vec, i + 1, target);// 递归
path.pop_back(); // 撤销选择
}
}
};
// 无重复元素的全排列
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
private:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
// 增加选择
used[i] = true;
path.push_back(nums[i]);
// 回溯
backtracking(nums, used);
// 撤销选择
path.pop_back();
used[i] = false;
}
}
};
// 有重复元素的全排列
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
// 重复计数
unordered_map<int, int> umap;
for (auto i : nums) ++umap[i];
backtrace(umap, 0, nums.size());
return res;
}
private:
vector<vector<int> > res;
vector<int> path;
void backtrace(unordered_map<int, int> &umap, int k, int total) {
if (k == total) {
res.push_back(path);
return;
}
for (auto& p : umap) { // 每轮递归结束会进入循环
if (p.second == 0) continue;
--p.second;
path.push_back(p.first);
backtrace(umap, k + 1, n);
++p.second;
path.pop_back();
}
}
};
-
动态规划算法
cpp// dp的推导 // - dp[j]为容量为j的背包所背的最大价值 // - 每次物品有两个选择 // - 放入则背包减去重量并增加价值 dp[j - weight[i]] + value[i] // - 不放入则仍为 dp[j] // 最终递推公式为dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); int main() { // 子功能部分 auto bag_problem = [](vector<int> &weight, vector<int> &value, int bag_weight)->int{ vector<int> dp(bag_weight + 1, 0); for (int i = 0; i < weight.size(); ++i) { // 倒叙保证物品只添加一次,顺序会导致所用数据是刚更新的 // 而不是上一层滚动的 for (int j = bag_weight; j >= weight[i]; --j) { dp[j] = max(dp[j], dp[j-weight[i]] + value[i]); } } return dp[bag_weight]; }; // 逻辑部分 vector<int> weight = {1, 3, 4}; vector<int> value = {15, 20, 30}; int bag_weight = 4; cout << bag_problem(weight, value, bag_weight); }
< a l g o r i t h m > 常用函数模板 <algorithm>常用函数模板 <algorithm>常用函数模板
-
前提:需要包含
#include<algorithm>
头文件 -
常见功能函数使用示例
cpp#include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { vector<int> vec{1, 2, 3, 4, 5}; // max和min函数 int min_val = min(a, b); // 返回a和b中较小的值 int max_val = max(a, b); // 返回a和b中较大的值 // sort函数:对容器进行自定义的排序 sort(vec.begin(), vec.end()); // 默认为升序排序 sort(vec.begin(), vec.end(), [](int a, int b){ return a < b; // 可以进行自定义 }); // 降序排序 // find函数:返回容器中指定值的迭代器,如果没有则返回end() auto it = find(vec.begin(), vec.end(), 3); if (it != vec.end()) cout << "找到了"; // replace函数:将容器中的所有a值替换成b值 replace(v.begin(), v.end(), 3, 10); // 将所有3替换成10 // reverse函数:反转vector中的元素 reverse(vec.begin(), vec.end()); // count函数:计算在一个范围内某个值的出现次数 int n = count(vec.begin(), vec.end(), 3);// 注意若为字符使用'3' // swap函数:交换两个变量的值 swap(a, b); // 使用lower_bound函数查找第一个大于等于3的元素位置 auto it = lower_bound(vec.begin(), vec.end(), 3); cout << it - vec.begin() << endl; }
面试基础
面试常见手撕题目
-
快速排序
cppvoid QuickSort(vector<int> &vec, int left, int right) { // 功能性函数:划分 auto partition = [](vector<int> &vec, int left, int right)->int{ int pivot = vec[left]; // 定义第一个为枢纽 while (left < right) { // 从右向前找比枢纽值小的放在左边 while (left < right && vec[right] >= pivot) --right; vec[left] = vec[right]; // 从左向后找比枢纽值大的放在右边 while (left < right && vec[left] <= pivot ) ++left; vec[right] = vec[left]; } // 填入枢纽值 vec[left] = pivot; return left; }; // 递归出口(需要使用大于等于) if (left >= right) return ;// [left, right]中left=right,表示区间有序 // 递归体 int pivot_index = partition(vec, left, right); QuickSort(vec, left, pivot_index-1); QuickSort(vec, pivot_index+1, right); }
-
合并两个有序链表
- 合并k个有序链表:使用合并两个有序链表作为基础进行归并算法
cppListNode* MergeList(ListNode* list1, ListNode* list2) { // 健壮性检查 if (list1 == nullptr || list2 == nullptr) return (list1 != nullptr) ? list1 : list2; // 初始化 TreeNode *vhead = ListNode(-1); TreeNode *cur = vhead; // 算法 while (list1 != nullptr && list2 != nullptr) { if (list1.val < list2.val) { cur.next = list1; list1 = list1.next; } else { cur.next = list2; list2 = list2.next; } cur = cur.next; } // 收尾 cur.next = (list1 != nullptr) ? list1 : list2; return vhead; }
-
求第k大数(含有重复数)
cpp#include <algorithm> #include <vector> #include <algorithm> using namespqce std; int KthLargeElement(vector<int> &vec, int k) { // 健壮性检查 if (k <= 0 || k > vec.size()) return INT_MIN; // 初始化 sort(vec.begin(), vec.end(), [](int a, int b){ return a > b; }); int count = 1; // 算法部分 for (int i = 1; i < vec.size(); ++i) {// key:相邻遍历的方式 if (vec[i] != vec[i-1]) ++count; if (count == k) break; } // 收尾 return vec[i]; }
基本操作
-
去重
cpp#include <iostream> #include <vector> #include <unordered_set> using namespace std; int main() { // 基本去重 vector<int> vec = { 1, 2, 3, 1, 3 }; // 使用set去重的天然特性,然后再赋值给原容器 unordered_set<int> uset(vec.begin(), vec.end()); vec = vector<int>(uset.begin(), uset.end());// key return 0; }
-
遍历相邻元素
cppint sum = 0; for (int i = 1; i < vec.size(); ++i) { sum += vec[i] - vec[i-1]; }
-
字符串转换
-
进制转换
-
删除链表next结点
cppauto delete_node = [](TreeNode *cur){ if (cur != nullptr) { ListNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; } };
-
字符串切割
cpp在这里插入代码片
项目基础
设计模式
-
消息队列(生产者消费者模式)
cpp#include <iostream> #include <condition_variable> #include <mutex> #include <queue> #include <string> #include <thread> using namespace std; class MessageQueue { public: MessageQueue() {} // 生产者放入消息队列中 void PushMsq(string msg) { unique_lock<mutex> lock(mtx_);// 1.上锁:保证{}范围内的互斥访问 que_.push(msg); // 2.生产:向消息队列中添加消息 cv_.notify_one();// 3.唤醒:唤醒在该条件变量上等待队列优先级最高的一个线程 // m_cv.notify_all()会唤醒所有线程,但是会造成资源争用,要谨慎使用 } // 消费者从消息队列中取出信息 string PopMsq() { unique_lock<mutex> lock(mtx_);// 1. 上锁 // 2. 队列为空则等待:如果队列为空,等待生产者添加消息 while (que_.empty()) { cv_.wait(lock);// 释放lock锁并阻塞等待 } // 3. 消费:取出消息并返回 string msg = que_.front(); que_.pop(); return msg; } private: // 记住这个顺序:先加智能锁,然后压入队列,最后唤醒条件变量上的线程 mutex mtx_; // 互斥锁:保证消息队列和条件变量的互斥访问 queue<string> que_; // 消息队列:生产者和消费者的缓冲区 condition_variable cv_; // 条件变量:保证生产者和消费者的同步 }; // 定义生产者线程函数 void producer(MessageQueue& mq) { for (int i = 0; i < 10; ++i) { string msg = "message " + to_string(i); mq.PushMsq(msg); this_thread::sleep_for(chrono::milliseconds(100)); // 生产者线程休眠一段时间 } } // 定义消费者线程函数 void consumer(int id, MessageQueue& mq) { for (int i = 0; i < 5; ++i) { string msg = mq.PopMsq(); cout << "consumer " << id << " get message: " << msg << std::endl; this_thread::sleep_for(chrono::milliseconds(200)); // 消费者线程休眠一段时间 } } // 测试生产者消费者模型 int main() { MessageQueue msq; // 线程的创建:参数为(函数指针,函数形参) thread t1(producer, ref(msq)); thread t2(consumer, 1, ref(msq)); thread t3(consumer, 2, ref(msq)); thread t4(consumer, 3, ref(msq)); // .join()执行完当前线程再向下执行 t1.join(); t2.join(); t3.join(); t4.join(); return 0; }
-
线程安全的单例模式
cpp// 饿汉式 class SinglePatter { public: static SinglePatter& GetInstance() { static SinglePatter instance; return instance; } private: SinglePatter(){}; SinglePatter(SinglePatter &) = delete; SinglePatter& operator=(const SinglePatter &) = delete; }; // 懒汉式 class SinglePatter { public: static SinglePatter *GetInstance() { unique_lock<mutex> lock(mtx); if (instance == nullptr) { instance = new SinglePatter(); } return instance; } private: static SinglePatter *instance; static mutex mtx; SinglePatter(){}; SinglePatter(SinglePatter &) = delete; SinglePatter& operator=(const SinglePatter &) = delete; };
高并发相关
-
写一个自旋锁
cpp// 自旋锁 int xchg(volatile int *addr, int new_val) { int res; asm volatile( // 将lock xchg换位cmpxhg是否就是CAS锁 "lock xchg %0, %1" :"+m"(*addr),"=a"(res) :"1"(new_val) :"cc" ); return res; } int locked = 0; void lock(){ while (xchg(&locked, 1)); } void unlock(){ xchg(&locked, 0); }
场景题目
智力题
- 数学归纳法(动态规划核心公式的推导)
- 推导前三个或者五个简单的输入和输出,从而假设递进关系式
- 再使用两个进行验证
- 组合排列问题
待解决问题
-
功能性函数auto封装导致的代码优雅性问题,字节二面上下左右走格子中,使用回溯增加复杂性,但是代码优雅易于理解。
-
匿名函数只是一个对数据的单纯的逻辑处理,不应该有健壮性检查和返回值,数据的初始化部分应该由实参传输,除内部工作变量外,其他变量应该由外部提供。