美团点评2020校招后台开发方向笔试题 练习解题报告 Apare_xzc

美团点评2020校招后台开发方向笔试题

2020.8.6

题目牛客链接:美团点评2020校招后台开发方向笔试题


5/9 [编程题]表达式求值

        给出一个布尔表达式的字符串,比如:true or false and false,表达式只包含true,false,and和or,现在要对这个表达式进行布尔求值,计算结果为真时输出true、为假时输出false,不合法的表达时输出error(比如:true true)。表达式求值是注意and 的优先级比 or 要高,比如:true or false and false,等价于 true or (false and false),计算结果是 true。
在这里插入图片描述
在这里插入图片描述

分析:类似中缀表达式求值。一个操作数栈存操作数(true/false),一个操作符栈存操作符(and/or) 因为and的优先级较高,所以在or入栈时要先把前面所有and弹栈计算。

#include <bits/stdc++.h>
using namespace std;
vector<string> v;
char str[100000];
string cal(string x,string y,string op) {
    if(op=="and") return (x=="true"&&y=="true")?"true":"false";
    else return (x=="true"||y=="true")?"true":"false";
}
int main(void) {
    cin.getline(str,10000);
    string ss;
    int len = strlen(str);
    int i = 0;
    while(str[i]==' ') ++i;
    for(;i<=len;++i) {
        if(str[i]==' '||str[i]==0) v.push_back(ss),ss="";
        else ss += str[i];
    }
    stack<string> num;
    stack<string> op;
    for(i=0;i<(int)v.size();++i) {
        if(v[i]=="true"||v[i]=="false") num.push(v[i]);
        else if(v[i]=="and") {
            op.push(v[i]);
        }
        else {
            while(!op.empty()&&op.top()=="and") {
                string tp1,tp2;
                if(num.empty()) {
                    puts("error");return 0;
                }
                tp1 = num.top();num.pop();
                if(num.empty()) {
                    puts("error");return 0;
                }
                tp2 = num.top();num.pop();
                num.push(cal(tp1,tp2,op.top()));
                op.pop();
            } 
            op.push(v[i]);
        }
    }
    while(!op.empty()) {
         string tp1,tp2;
                if(num.empty()) {
                    puts("error");return 0;
                }
                tp1 = num.top();num.pop();
                if(num.empty()) {
                    puts("error");return 0;
                }
                tp2 = num.top();num.pop();
                num.push(cal(tp1,tp2,op.top()));
                op.pop();
    }
    if(num.empty()) {
        puts("error");return 0;
    }
    string ans = num.top();
    num.pop();
    if(num.empty()) cout<<ans<<endl;
    else puts("error");
    
    return 0;
}

6/9 [编程题]字符串模式匹配

给出两个字符串,分别是模式串P和目标串T,判断模式串和目标串是否匹配,匹配输出 1,不匹配输出 0。模式串中‘?’可以匹配目标串中的任何字符,模式串中的 ’*’可以匹配目标串中的任何长度的串,模式串的其它字符必须和目标串的字符匹配。例如P=a?b,T=acb,则P 和 T 匹配。
在这里插入图片描述
在这里插入图片描述

分析:

        因为字符串长度不大于20,所以我们可以进行O(n^2)的dp. dp[i][j]代表模式串P的前i位和目标串T的前j位是否匹配。我们知道,?可以代表任意一个字符,*可以代表任意长度的字符串(包括空串)。

  • 如果P[i]是?, 那么dp[i][j] = dp[i-1][j-1]
  • 如果P[i]是*,那么dp[i][j] = dp[i-1][j] || dp[i][j-1] || dp[i-1][j] (ab*,abc)(ab*,ab)(ab*,abcde)
  • 如果P[i]不是通配符,那么dp[i][j] = dp[i-1][j-1]&&P[i]==T[j]
  • 初始化操作:dp[0][0] = true, dp[i][0] = dp[i-1][0] && P[i]=='*',只有都是*才能和空串匹配
#include <bits/stdc++.h>
using namespace std;
bool dp[100][100];
char p[100],t[100];
int main(void) {
	cin>>(p+1)>>(t+1);
	int len1 = strlen(p+1);
	int len2 = strlen(t+1);
	dp[0][0] = true;
	for(int i=1;i<=len1;++i) {
		dp[i][0] = dp[i-1][0]&&p[i]=='*';
	}
	for(int i=1;i<=len1;++i) {
		for(int j=1;j<=len2;++j) {
			if(p[i]=='?'||p[i]==t[j]) dp[i][j] = dp[i-1][j-1]; 
			else if(p[i]=='*') dp[i][j] = dp[i-1][j]||dp[i][j-1]||dp[i-1][j-1];
		}
	}
	if(dp[len1][len2]) puts("1");
	else puts("0");
	return 0;
} 

7/9 [编程题]订单分配

打车派单场景, 假定有N个订单, 待分配给N个司机。每个订单在匹配司机前,会对候选司机进行打分,打分的结果保存在N*N的矩阵A, 其中Aij 代表订单i司机j匹配的分值。
假定每个订单只能派给一位司机,司机只能分配到一个订单。求最终的派单结果,使得匹配的订单和司机的分值累加起来最大,并且所有订单得到分配。
在这里插入图片描述
在这里插入图片描述

分析:dfs然后记录最佳答案即可

#include <bits/stdc++.h>
using namespace std;
const int N = 12;
double a[N][N];
double res = -1000;
int r[N],n;
int g[N];
bool vis[N];

void solve() {
	double ans = 0.0;
	for(int i=1;i<=n;++i)
		ans += a[i][r[i]];
	if(ans>res) {
		res = ans;
		for(int i=1;i<=n;++i) g[i] = r[i];
	}
} 
void dfs(int x) {
	if(x==n+1) {
		solve();return;
	}
	for(int i=1;i<=n;++i) {
		if(!vis[i]) r[x]=i,vis[i]=true,dfs(x+1),vis[i]=false;
	}
}
int main(void) {
	cin>>n;
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n;++j)
			cin>>a[i][j];
	}
	dfs(1);
	printf("%.2f\n",res);
	for(int i=1;i<=n;++i) cout<<i<<" "<<g[i]<<endl;
	return 0;
} 

8/9 [编程题]美团骑手包裹区间分组

2110年美团外卖火星第3000号配送站点有26名骑手,分别以大写字母A-Z命名,因此可以称呼这些骑手为黄家骑士特工A,黄家骑士特工B…黄家骑士特工Z,某美团黑珍珠餐厅的外卖流水线上会顺序产出一组包裹,美团配送调度引擎已经将包裹分配到骑手,并在包裹上粘贴好骑手名称,如RETTEBTAE代表一组流水线包裹共9个,同时分配给了名字为A B E R T的5名骑手。请在不打乱流水线产出顺序的情况下,把这组包裹划分为尽可能多的片段,同一个骑手只会出现在其中的一个片段,返回一个表示每个包裹片段的长度的列表。
在这里插入图片描述
在这里插入图片描述
MPMPCPMCMDEFEGDEHINHKLIN

分析:

  • 可以统计出每个字母最后一次出现的位置,记为ed[ch]
  • 维护left指针和right指针,从前往后遍历,当前字符为ch,用ed[ch]更新right。直到i==right,则可以切割,贪心遍历即可。
#include <bits/stdc++.h>
using namespace std;
//MPMPCPMCMDEFEGDEHINHKLIN
char str[10000];
int st[300],ed[300]; //st[]其实没有用到
int main(void) {
	scanf("%s",str);
	memset(st,-1,sizeof(st));
	int i;
	for(i=0;str[i];++i) {
		int ch = str[i];
		if(st[ch]==-1) st[ch] = i;
		else ed[ch] = i;
	}
	int len = i;
	vector<int> v;
	int left = 0,right = 0;
	for(i=0;i<len;++i) {
		right = max(right,ed[str[i]]);
		if(i==right) {
			v.push_back(right-left+1);
			left = i+1;
			right = i+1;
		} 
	}
	for(auto x:v)
		cout<<x<<" ";cout<<endl;
	return 0;
} 

9/9 [编程题]火星文字典

已知一种新的火星文的单词由英文字母(仅小写字母)组成,但是此火星文中的字母先后顺序未知。给出一组非空的火星文单词,且此组单词已经按火星文字典序进行好了排序(从小到大),请推断出此火星文中的字母先后顺序。
在这里插入图片描述
在这里插入图片描述
wrt wrf er ett rftt
wertf

分析:

  • 字典序的概念我们知道,是从头开始比较,只要两个字符串相同位置的字符不同,我们就可以比出来优先级。
  • 然后我们可以按照按照字符优先级建立有向图,然后拓扑排序。如果不存在环并且拓扑序唯一,则输出拓扑序,否则输出invaild
  • 如果拓扑排序的时候入度为0的结点不止一个,那么无法确定顺序
  • 如果拓扑排序之后没有访问到所有n个结点,说明有环
#include <bits/stdc++.h>
using namespace std;
vector<string> v;
vector<int> edge[300];
int du[300];
map<pair<int,int>,int> mp;
set<char> st;
void addedge(int x,int y) {
	pair<int,int> pr(x,y);
	if(mp.count(pr)) return;
	mp[pr] = 1;
	edge[x].push_back(y);
	du[y]++;
}
void cal(string x,string y) {
	int lenx = x.length();
	int leny = y.length();
	int i = 0, j = 0;
	while(i<lenx&&j<leny) {
		if(x[i]==y[j]) ++i,++j;
		else {
			addedge(x[i],y[j]);
			break;
		}
	} 
}
int main(void) {
	string s;
	int n = 0;
	while(cin>>s) {
		++n;
		v.push_back(s);
		for(auto ch:s)
			st.insert(ch);
	}
	for(int i=0;i<n-1;++i) {
		cal(v[i],v[i+1]);
	}
	int cnt = 1;
	queue<int> Q;
	int num = 0;
	for(auto x:st) {
		++num;
		if(du[x]==0) Q.push(x); 
	}
	string res;
	while(!Q.empty()) {
		if(Q.size()!=1) {
			puts("invalid");return 0;
		}
		int tp = Q.front();Q.pop();
		res += tp;
		int len = edge[tp].size();
		for(int i=0;i<len;++i) {
			int to = edge[tp][i];
			du[to]--;
			if(du[to]==0) Q.push(to);
		}
	}
	if(res.length()==num) cout<<res<<endl;
	else puts("invalid");
	
	return 0;
} 

问答题及答案

  1. RESTful调用和 RPC调用有什么区别?如果让你设计一个RPC服务治理框架你会设计那些模块?是否了解过Service Mesh,如果了解Service Mesh是用来解决什么问题的?

1、RESTful是一种软件架构风格,用于约束客户端和服务器交互,满足这些约束条件和原则的应用程序或设计就是 RESTful。比如HTTP协议使用同一个URL地址,通过GET,POST,PUT,DELETE等方式实现查询、提交、删除数据。RPC是远程过程调用,是用于解决分布式系统服务间调用的一种方式。RPC采用客户端与服务端模式,双方通过约定的接口(常见为通过IDL定义或者是代码定义)以类似本地方法调用的方式来进行交互,客户端根据约定传输调用函数+参数给服务端(一般是网络传输TCP/UDP),服务端处理完按照约定将返回值返回给客户端。

重点为RESTful HTTP的约束风格,RPC调用模型。
1、可分为两大部分RPC +服务治理
RPC部分 = IDL +客户端/服务端实现层 +协议层 +数据传输层
服务治理 =服务管理(注册中心) +服务监控 +服务容灾 +服务鉴权

2、 Service Mesh为了解决传统微服务框架"胖客户端"方式,引入的如下问题:
与业务无关的服务治理逻辑与业务代码强耦合,框架、SDK的升级与业务代码强绑定,多语言的胖客户端支持起来性价比极低。


  1. 请描述避免多线程竞争时有哪些手段?
  1. 不可变对象;
  2. 互斥锁;
  3. ThreadLocal 对象;
  4. CAS;
  1. 临界区:通过对多线程的串行化来访问公共资源,速度快,适合控制数据访问
  2. 互斥量:在对内存进行读写访问时给访问资源上锁,此时其他线程无法对上锁资源进行处理
  3. 信号量:控制剧有限数量用户资源而设计,通过一个计数器减操作完成对资源的访问,然后转交给其他线程,另外线程的计数器在通信线程减操作
    的同时进行加操作,通过计数器实现通信
  4. 条件变量:通过阻塞,和通知解除阻塞来避免竞争
  5. 读写锁: 读时不上锁,写时上锁,如果资源允许脏读会比较合适
  6. 原子锁, 通过原子操作, Interlocked.CompareExchange实现“无锁”竞争
    7.原子性操作, 野外原子性操作天生线程安全,不需要加所

  1. 请简述HTTP的5个常用Method及其含义,以及5个常用Status Code及其含义?HTTP与HTTPS的区别是什么,简述一下HTTPS的实现原理。
链接:https://www.nowcoder.com/questionTerminal/92a370c61977486495caf2c916dd09ec
来源:牛客网

get 从服务器端获取资源
put 提交资源
post 更新资源
delete 删除资源
connect 建立tunnel隧道
100 请求已收到,正等待后续资源
200 ok 成功
206 partial content 部分资源
301 永久重定向
400 bad request 客户端请求语法错误

https是http加上ssl的应用层协议。在http的基础上增加了安全性和可靠性。
端口的不同:http默认是80端口, https默认是443端口
安全性:http是明文传输,https是密文传输。
认证:http没有认证,https在建立TCP连接前会进行ssl层的握手,在这个过程中需要认证。
成本上:https的证书需要成本,同时加密和解密时对CPU和内存开销增加。
https的原理:
https通信时,首先建立ssl层的连接,客户端将ssl版本号和加密组件发到客户端,客户端收到后对ssl版本号和加密组件进行匹配,同时将CA证书及密钥发送到客户端。客户端对证书进行验证,验证通过后使用非对称加密对数据通信时的密钥进行协商。协商后得到一致的获得一致的对称加密密钥。然后使用对称加密算法进行TCP连接,后续的过程跟http的过程一致。三次握手,数据交换,四次挥手,通信结束。
发表于 2020-03-19 10:05:25

猜你喜欢

转载自blog.csdn.net/qq_40531479/article/details/107835718