交易系统开发技能及面试之TechCoding
Posted BBinChina
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了交易系统开发技能及面试之TechCoding相关的知识,希望对你有一定的参考价值。
文章目录
- 概要
- 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][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i)
//不持有时的收益
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
//持有时的收益
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
//不持有时的收益
return dp[n-1][0];
//贪心算法
static int SumBuySell2(vector<int> prices)
if (prices.empty())
return 0;
int result = 0;
for (int i = 1; i < prices.size(); ++i)
if (prices[i] > prices[i - 1])
result += (prices[i] - prices[i - 1]);
return result;
;
Q6 位运算:反转一个uint32整数的位值
class ReverseBits
public:
static uint32_t Reverse(uint32_t num)
for (int i = 31, j = 0; i > j; --i, ++j)
//确认i j 位的bit值是否有设置
bool iset = (num & (1 << i)) != 0;
bool jset = (num & (1 << j)) != 0;
//排除i\\j位均为0的情况
if ((iset && !jset) || (jset && !iset))
//异或:两个位相同为0,相异为1, 实现反转
num = (num ^ (1 << i));
num = (num ^ (1 << j));
return num;
;
Q7 实现pow 幂等计算
class Pow
public:
static double CalculatePow(double x, int n)
long long pow = n;
//如果n小于0,采用1.0除与pow(x,abs(n))的结果值
bool isNeg = n < 0;
//abs(n)
pow = isNeg ? -pow : pow;
double res = 1.0;
double xpow = x;
while (pow != 0)
if (pow & 1)
res *= xpow;
xpow *= xpow;
//采用位运算
pow >>= 1;
return isNeg ? 1.0 / res : res;
;
Q8 转换excel的列名如 1->A, 2->B , 26->Z, 28->AB…
#include <string>
class ConvertToTitle
public:
static std::string Convert(int i)
std::string col;
while (i > 26)
col += 'A' + (i / 26 - 1);
i /= 26;
if (i > 0)
col += 'A' + i - 1;
return col;
;
Q9 移除单向链表倒数第n个节点
#pragma once
class ListNode
public:
ListNode* next = nullptr;
;
/// <summary>
/// 移除单向链表倒数第n个节点
/// 可以设置两个指针,p1 p2 ,p2比p1先前进n个节点,然后同时移动p1,p2
/// 当p2先到达链尾时,说明p1即在倒数第n个节点上
/// </summary>
class RemoveNthFromEnd
public:
static ListNode* Remove(ListNode* head, int n)
ListNode* p1, * p2, * prev;
prev = p1 = p2 = head;
while (n--)
p2 = p2->next;
while (p2)
prev = p1;
p1 = p1->next;
p2 = p2->next;
//移除倒数第n个节点
prev->next = p1->next;
return head;
;
10 给定一个字符串,只包含字符 ‘(’ , ‘)’ , ‘[’, ‘]’ , ‘’ , ‘’ 要求判断输入的字符串是否符合要求:开闭配对,以及顺序正确
example:
input : ()
output: true
input : ()[]
output: true
input: ([
output: false
这道题可以采用stack数据结构来解决,当字符为 左符号时压栈,遇到右符号时判断栈顶符号是否当匹配的左符号,如果不匹配则返回false
#pragma once
#include <string>
#include <stack>
using namespace std;
class MatchParentTheses
public:
static bool IsOpen(char c)
return c == '(' || c == '[' || c == '';
static char GetClose(char c)
if (c == '(') return ')';
else if (c == '[') return ']';
else if (c == '') return '';
static bool IsValid(string s)
stack<char> stk;
for (auto c : s)
if (IsOpen(c))
stk.push(c);
else
if (stk.empty()) return false;
if (GetClose(stk.top()) != c) return false;
stk.pop();
return stk.empty();
;
11 给定一个数组的字符串,实现将相同字符组成的字符串归为一组
比如输入:[“eat”,“ate”,“tan”,“eta”,“nta”,“bat”]
输出:[
[“eat”,“ate”,“eta”] ,
[“tan”,“nta”],
[“bat”]
]
思路:
相同字符组成,只是顺序不同,那么可以采用将字符串进行排序后,作为该字符串的key,相同key的字符串归为一组
#pragma once
#include <vector>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
class GroupAnagrams
public:
static vector<vector<string>> convertAnagrams(vector<string>& arr)
//unordered无序的hashmap,用于记录相同组的字符串
unordered_map<string, vector<string>> strMap;
for (auto& s : arr)
string tmp = s;
//将字符串进行排序用于做key
sort(tmp.begin(), tmp.begin());
strMap[tmp].push_back(s);
vector<vector<string>> res;
for (auto& itr : strMap)
res.push_back(itr.second);
return res;
;
12 给定一组数字,进行一次排序,排序后的数字值应该是所有可能排序结果中最小的。
比如 输入:1, 2, 3 输出: 1, 3, 2
输入 3, 2, 1 输出 1, 2, 3
输入 1, 1, 5 输出 1, 5, 1
对于长度为 nn 的排列 aa:
首先从后向前查找第一个顺序对 (i,i+1)(i,i+1),满足 a[i] < a[i+1]a[i]<a[i+1]。这样「较小数」即为 a[i]a[i]。此时 [i+1,n)[i+1,n) 必然是下降序列。
如果找到了顺序对,那么在区间 [i+1,n)[i+1,n) 中从后向前查找第一个元素 jj 满足 a[i] < a[j]a[i]<a[j]。这样「较大数」即为 a[j]a[j]。
交换 a[i]a[i] 与 a[j]a[j],此时可以证明区间 [i+1,n)[i+1,n) 必为降序。我们可以直接使用双指针反转区间 [i+1,n)[i+1,n) 使其变为升序,而无需对该区间进行排序。
#pragma once
#include <vector>
#include <algorithm>
using namespace std;
class NextPermutation
public:
static void sortNextPermutation(vector<int>& nums)
int i = 0;
//查找左边较小值
for (i 以上是关于交易系统开发技能及面试之TechCoding的主要内容,如果未能解决你的问题,请参考以下文章