文章目录
- 概要
- Q1设计股票订单撮合系统,支持5000以上不同品种,单品种每天上百万订单
- Q2 设计订单簿,用于支持下单、撤单、撮合功能,撮合采用价优方式
- Q3 设计一个网关用于接收用户订单并将其转发给交易撮合服务,该网关需要实现限流功能,给定 N,T两个值,表示在T微妙时间内只处理N个消息,相当于滑动窗口控制。要求设计订单控制类:根据N、T、Input,处理接收的订单流,返回pass 或者 fail。
- Q4 实现前缀树的插入跟搜索
- Q5 给定一个存储股票价格的数组,代表某股票每天的价格,采用t+1方式,当天买隔天卖。计算出最优的盈利价格,比如:
- Q6 位运算:反转一个uint32整数的位值
- Q7 实现pow 幂等计算
- Q8 转换excel的列名如 1->A, 2->B , 26->Z, 28->AB...
- Q9 移除单向链表倒数第n个节点
- 10 给定一个字符串,只包含字符 '(' , ')' , '[', ']' , '{' , '}' 要求判断输入的字符串是否符合要求:开闭配对,以及顺序正确
- 11 给定一个数组的字符串,实现将相同字符组成的字符串归为一组
- 12 给定一组数字,进行一次排序,排序后的数字值应该是所有可能排序结果中最小的。
- 13 输入一个二维数组表示一系列x,y坐标点,以及k值,要求输出k个最接近0坐标的坐标集
- 14 实现sqrt开平方根方法,输入x为非负整型,输出x的平方根(向下取整)
- 15 经典爬楼梯,假设每次只能爬 1格或者2格,那么n层楼梯可以有几种方式爬到楼顶
- 16 假设一个班级有N个学生,给定一个 N*N的矩阵 M,表示学生间的朋友关系,即如果M[i][j] =1那么i 跟 j两位学生为好友关系,为0即相反。要求算出班级内有多少个学生是好友关系
- 17 给定一组 区间,实现将有重叠的区间进行合并,输出合并后的区间组
- 18 给定一个n大小的整型数组m,从数组中获取3个其合为0的整型为一组,输出所有的组合可能,不可存在相同数据的组合
- 19 判断一个树是否为二叉查找树(BST)
- 20 输入一个二叉树,要求输出每层节点值
概要
编程面试是整体面试中最核心的部分,毕竟开发者都是手艺者,需要动手跟思维都达到公司候选人的要求,而这部分通常包括以下几个知识要点:
1、链表、二叉树、字典树等数据结构
2、使用哈希表、栈、队列、优先队列来解决问题
3、数组、字符串的搜索及排序
4、位运算在低时延开发过程中是优化利器
5、动态规划虽然是算法中的经典也是最难的,但通常用的比较少,可以查看大厂是否有相关面试题
6、路径算法通常也较少出现,但推荐掌握BFS/DFS算法
关于面试题,建议多刷leetcode
Q1设计股票订单撮合系统,支持5000以上不同品种,单品种每天上百万订单
因为每个品种的订单簿是隔离的,相互不影响,所以采用独立线程或者独立进程处理某个品种的交易订单,这样做的目的可以让品种的处理进程水平扩展,可以将不同品种的订单处理部署在不同的机器,再通过交易网关进行路由。可以根据对品种的交易量进行预测,对交易活跃的品种升级CPU或者内存完成垂直扩展。
Q2 设计订单簿,用于支持下单、撤单、撮合功能,撮合采用价优方式
//订单簿由买方、卖方两个二叉树构成
struct OrderBook {
Limit* buyTree;
Limit* sellTree;
Limit* lowestSell;
Limit* highestBuy;
};
//二叉树节点记录当前价格档位以及订单数量
//节点上记录当前价位的订单队列(时间优先)
struct Limit {
int limitPrice;
int size;
Limit* parent;
Limit* leftChild;
Limit* rightChild;
Order* headOrder;
Order* tailOrder;
};
//订单只需唯一标识id、交易方向、数量、价格
//采用列表方式存储订单
//
struct Order {
int orderId;
bool buyOrSell;
int num;
int limitPrice;
Order* nextOrder;
Order* prevOrder;
Limit* parentLimit;
};
//采用订单id为key, 订单为value,用于撤单
map<int, Order*> orderMap;
//采用limitPrice为key, Limit节点为value, 用于
map<int, Limit*> sellLimitPriceMap;
map<int, Limit*> buyLimitPriceMap;
采用订单id为key,通过map存储所有的订单
1、下单
当下单时,如果二叉树不存在该价位的订单,那么添加二叉树节点,时间复杂度为O(log m)。
如果limitPriceMap存在该价位,那么时间复杂度:O(1)
2、撤单
通过orderMap进行索引,时间复杂度 : O(1)
3、成交
通过map找到对手价的节点,遍历节点的订单列表进行撮合
4、获取最高最低价
通过OrderBook的lowestSell、lowestBuy指针进行查找
Q3 设计一个网关用于接收用户订单并将其转发给交易撮合服务,该网关需要实现限流功能,给定 N,T两个值,表示在T微妙时间内只处理N个消息,相当于滑动窗口控制。要求设计订单控制类:根据N、T、Input,处理接收的订单流,返回pass 或者 fail。
N:5 T:20
消息流:
1640277839000
1640277839000
1640277839012
1640277839014
1640277839014
1640277839014
1640277839015
1640277839020
1640277839037
1640277839039
输出:
1640277839000 pass
1640277839000 pass
1640277839012 pass
1640277839014 pass
1640277839014 pass
1640277839014 fail
1640277839015 fail
1640277839020 fail
1640277839037 pass
1640277839039 pass
要点:
采用queue队列来接收消息,在限制时间T内,队列的大小不能超过N个。
/*
实现订单网关的限流
给定 n,t两个参数
表示在t毫秒内,只处理n个order
*/
#include <deque>
class rateLimiter {
private:
//缓冲窗口队列,接收订单的时间戳
std::deque<uint64_t> _dq;
int _numMsgs;
int _timeWindow;
public:
rateLimiter(int n, int t) : _numMsgs(n), _timeWindow(t) {
}
/// <summary>
/// 判断订单是否可被处理
/// </summary>
/// <param name="ts">订单的时间戳</param>
/// <returns></returns>
bool IsRateLimiterOK(uint64_t ts) {
if (_dq.empty()) {
_dq.push_front(ts);
return true;
}
// 最新的订单时间戳在对头,队尾的订单如果与新订单的时间戳超过了时间窗口,那么可以弹出,剩余的表示在时间窗口内已处理的订单
while (!_dq.empty() && (ts - _dq.back() >= _timeWindow)) {
_dq.pop_back();
}
// 已出来的数量小于被允许的数量,说明新订单可被处理,记录其时间戳并返回true
if (_dq.size() < _numMsgs) {
_dq.push_front(ts);
return true;
}
return false;
}
int GetCountInWindow() {
return _dq.size();
}
};
Q4 实现前缀树的插入跟搜索
/*
前缀树 也叫字典树,常用于压缩字典内容
*/
#include <string>
using namespace std;
class PreFixTree {
struct Node {
//需要确认字符内容不超过26个字母
Node* child[26];
bool isWord;
//初始化子节点为nullptr
Node() {
for (int i = 0; i < 26; i++) {
child[i] = nullptr;
}
isWord = false;
}
};
Node* root;
public:
PreFixTree() {
root = new Node();
}
void Insert(string word) {
Node* curr = root;
for (auto ch : word) {
int index = ch - 'a';
//已存在相同字符的节点,继续遍历
if (curr->child[index]) {
curr = curr->child[index];
continue;
}
//新建节点
curr->child[index] = new Node;
curr = curr->child[index];
}
curr->isWord = true;
}
bool Search(string word) {
Node* curr = root;
for (auto ch : word) {
int index = ch - 'a';
//不存在节点
if (!curr->child[index]) {
return false;
}
curr = curr->child[index];
}
return curr->isWord ? true : false;
}
};
Q5 给定一个存储股票价格的数组,代表某股票每天的价格,采用t+1方式,当天买隔天卖。计算出最优的盈利价格,比如:
输入:[7,1,5,3,6,4]
输出:5
即买入价格 1 ,到 卖出价格为6时,收益最大为5.
#include <vector>
#include <math.h>
using namespace std;
//给出股票的价格数组,找出最佳买卖点,得出收益
class FindStockBuySell {
public:
static int FindResult(vector<int> prices) {
if (prices.empty()) {
return 0;
}
int result = 0;
int minPrice = prices[0];
for (int i = 1; i < prices.size(); ++i) {
result = max(result, prices[i] - minPrice);
minPrice = min(minPrice, prices[i]);
}
return result;
}
//动态规划
static int SumBuySell(vector<int> prices) {
if (prices.empty()) {
return 0;
}
int n = prices.size();
int** dp = new int*[n];
for (int i = 0; i < n; ++i) {
dp[i] = new int[2];
}
dp[0]