交易系统开发技能及面试之TechCoding

Posted BBinChina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了交易系统开发技能及面试之TechCoding相关的知识,希望对你有一定的参考价值。

概要

编程面试是整体面试中最核心的部分,毕竟开发者都是手艺者,需要动手跟思维都达到公司候选人的要求,而这部分通常包括以下几个知识要点:

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;
	
;

以上是关于交易系统开发技能及面试之TechCoding的主要内容,如果未能解决你的问题,请参考以下文章

交易系统开发技能及面试之TechCoding

交易系统开发技能及面试之低延迟编程技术

交易系统开发技能及面试之低延迟编程技术

交易系统开发技能及面试之低延迟编程技术

交易系统开发技能及面试之网络知识

交易系统开发技能及面试之网络知识