刚做完网易在线笔试题,感触最深的地方是,虽然题目形式和ACM题目相似,但是内容更偏向于实际应用。总共有四个题目,第一个题目属于字符串匹配类型,难度较低,第二个题目是模拟SQL语句的输出,第三个题目是KNN算法,第四个题目是贝叶斯算法。题目偏基础,算法思想很容易想到,但如果平常从来没写过这类算法,再加上代码能力不是很强的话,写起来还是有点吃力的。下面是第一题,第三题,第四题的答案。
题目1 : 虚拟游戏世界实体分析
描述
-
Fruit{apple{shape,color},orange{taste,price}} Fruit Fruit{apple{shape,color},orange{taste,price}} orange Fruit{apple{shape,color},orange{color,price},color} color
样例输出
-
1 2 2,3
虚拟游戏世界里面有很多实体,实体可能由很多子实体或者子属性构成。由于实体之间可能有非常之多的嵌套,查询某个实体或者属性属于第几层嵌套,以便将来对虚拟世界的修改和展示是一项待解决的问题,作为未来的虚拟世界分析员,你能用程序解决这个问题吗?
输入
输入数据可能由多组数据构成,每组数据由两行构成:
第一行是对虚拟世界的描述,实体或者属性由英文字母或者数字构成,子实体和子属性紧接着父实体嵌套在{}中,兄弟实体或者属性用“,”分隔。
第二行是要查询的属性或者实体,有且仅有一个。
注意数据输入可能很大。
输出
输出为查询的实体或者属性的层数;如果属性不存在,输出-1;如果有多个结果满足查询,请从小到大输出所有去重之后的结果,用”,”分隔
样例输入#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
string s;
string query;
while(cin >> s >> query) {
int i = 0;
int j = 0;
int h = 1;
int begin = 0;
vector<int> ans;
for( i = 0; i < s.size(); i++) {
if(s[i] == '{' || s[i] == '}' || s[i] == ',') {
if(i - begin == query.size()) {
for(j = 0; j < query.size(); j++) {
if(query[j] != s[begin + j]) {
break;
}
}
if(j == query.size()) {
ans.push_back(h);
}
}
begin = i + 1;
}
if(s[i] == '{') {
h++;
} else if(s[i] == '}') {
h--;
}
}
sort(ans.begin(), ans.end());
if(ans.size() == 0)
cout << -1 << endl;
else {
cout << ans[0];
for(int k = 1; k < ans.size(); k++) {
if(ans[k] != ans[k-1])
cout << "," << ans[k];
}
cout << endl;
}
}
return 0;
}
题目3 : 游戏玩家分类
-
3 5 16 2 0.19 0.04 0.06 0.22 0.11 A 0.28 0.42 0.38 0.39 0.44 B 0.71 0.61 0.54 0.52 0.54 C 0.98 0.82 0.92 0.98 0.97 D 0.05 0.03 0.15 0.01 0.11 A 0.33 0.29 0.33 0.47 0.27 B 0.72 0.52 0.61 0.71 0.68 C 0.78 0.86 0.91 1.0 0.76 D 0.01 0.17 0.14 0.15 0.2 A 0.44 0.36 0.32 0.32 0.35 B 0.67 0.65 0.57 0.58 0.52 C 0.87 0.92 0.8 0.83 0.77 D 0.01 0.11 0.14 0.12 0.07 A 0.33 0.43 0.43 0.45 0.38 B 0.57 0.54 0.75 0.7 0.64 C 0.9 0.94 0.83 0.96 0.77 D 0.29 0.29 0.42 0.36 0.27 0.56 0.67 0.71 0.66 0.7
样例输出
-
B C
描述
理查德•巴图博士通过对游戏中玩家固定的行为模式进行观察,于1996年提出了巴图模型,尝试把玩家的不同行为模式进行分类。他将游戏玩家分成了成就型、探索型、社交型和杀手型。该分类方式本质上从玩家在游戏中的需求出发,根据具体的行为表现对其进行分类。推断玩家所属类型,对于游戏用户研究,精准营销投放都有非常重要的意义,因此对不同玩家进行分类是一项重要研究工作。为了实现分类模型,通过收集玩家在游戏中的不同行为数据并进行归一化,可以得到玩家的特征向量以及已知类型玩家的标签,如:
副本参与次数 | 竞技场参与次数 | 任务完成次数 | 登陆频率 | 充值额度 | 玩家类型 |
0.8 | 0.5 | 0.6 | 0.9 | 0.2 | A |
0.4 | 0.8 | 0.1 | 0.2 | 0.1 | B |
0.9 | 0.1 | 0.5 | 0.6 | 0.9 | C |
0.5 | 0.2 | 0.1 | 0.3 | 0.0 | D |
(其中前五列数字为玩家的特征向量,最后一列字母是玩家类型,有A、B、C、D四种取值)
分类问题有多种解决算法,其中K最近邻(k-Nearest Neighbor,KNN)分类算法是最简单的机器学习算法之一,其思想是:如果一个样本在特征空间中的k个最相似的样本中的大多数属于某一个类别,则该样本也属于这个类别。请用该方法实现游戏玩家分类,距离度量函数采用欧氏距离。
输入
每个输入数据包含一组训练数据和一组测试数据。
第一行第一个数为KNN算法的k(k<=10000)值,第二个数为特征向量的长度L(L<=100),第三个数M(M>k, M<=10000)为训练数据行数,第四个数N(N<=10000)为测试数据行数。之后是M行训练数据和N行测试数据。每行中数据使用空格分隔。
输出
对于每行测试数据,输出该玩家的类型,例如“A”。如果前K个相似类型中,出现数量最多的类型有重复,则一起输出,以ABCD升序排列,例如“AC”。
代码:
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef struct
{
vector<double> f;
char label;
}Elem;
typedef struct
{
double cost;
int idx;
}NN;
typedef struct
{
int cnt;
int idx;
}LabelCnt;
bool operator<(const NN &x, const NN &y)
{
return x.cost < y.cost;
}
bool operator<(const LabelCnt &x, const LabelCnt &y)
{
return x.cnt > y.cnt;
}
double distance(vector<double> &f1, vector<double> &f2) {
double sum = 0.0;
for(int i = 0; i < f1.size(); i++) {
sum += (f1[i] - f2[i]) * (f1[i] - f2[i]);
}
return sum;
}
int main()
{
int k,L,M,N;
while(cin >> k >> L >> M >> N) {
int i = 0;
int j = 0;
vector<Elem> trainData;
trainData.resize(M);
for( i = 0; i < M; i++) {
trainData[i].f.resize(L);
for( j = 0; j < L; j++) {
cin >> trainData[i].f[j];
}
cin >> trainData[i].label;
}
vector<Elem> testData;
testData.resize(N);
for( i = 0; i < N; i++) {
testData[i].f.resize(L);
for( j = 0; j < L; j++) {
cin >> testData[i].f[j];
}
vector<NN> nnCost;
nnCost.resize(M);
int t = 0;
for( t = 0; t < M; t++) {
nnCost[t].idx = t;
nnCost[t].cost = distance(trainData[t].f, testData[i].f);
}
sort(nnCost.begin(), nnCost.end());
vector<LabelCnt> labels;
labels.resize(4);
for( t = 0; t < labels.size(); t++) {
labels[t].cnt = 0;
labels[t].idx = t;
}
for( t = 0; t < k; t++) {
int idx = nnCost[t].idx;
int label = trainData[idx].label - 'A';
labels[label].cnt++;
}
sort(labels.begin(), labels.end());
vector<int> ans;
ans.push_back(labels[0].idx);
for( t = 1; t < labels.size(); t++) {
if(labels[t].cnt == labels[t-1].cnt)
ans.push_back(labels[t].idx);
else
break;
}
for( t = 0; t < ans.size(); t++) {
cout << (char)(ans[t]+'A');
}
cout << endl;
}
}
return 0;
}
题目4 : 好师父推算
-
1 2 3 fchatnum cchatnum remark 1 2 1 3 3 1 1 1 1 6 9 2 3 7 2 4 6 2 4 2 2 3 8 2 1 1 1 8 4 2 ……
样例输出
-
0.320 0.034,0.091,0.075,0.144,0.100,0.106,0.119,0.134,0.100,0.097 0.691
描述
师徒系统普遍存在于各类网络游戏中,对于游戏促进新手留存具有重要意义,现在采集到如下信息:
好友个数 聊天次数 是否是好师父 1 3 1 2 1 2
希望你用naïve bayes算法基于“好友个数”和“聊天次数”推算某玩家是好师父的概率,以方便产品优化匹配规则。
输入
输入数据由多行构成,每行中的数据用“\t”分隔。第1行是1~3个用“\t”分隔的数字,表示输出第几个问题的答案,第2行是属性名称,包括fchatnum,cchatnum和remark三个属性,分别代表好友个数、聊天次数和是否是好师父。从第3行开始为训练数据,含义与第2行的属性名称相对应。好友个数和聊天次数取值都是1~10的整数,是否是好师父取值是1~2的整数,其中2表示好师父。
输出
根据第1行输入数据指定的编号输出以下3个小题的答案,多个小题答案使用换行“\n”分割。
第1题:输出好师父的先验概率。
第2题:输出好师父群体中好友个数取值的概率分布,依次对应1~10的概率取值,零值也要输出,中间用逗号分隔。
第3题:输出给定fchatnum=9,cchatnum=9的玩家是好师父的概率。
输出结果统一四舍五入保留小数点后3位。
完整样例输入下载
总计1000条数据,请在这里下载。
代码:
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef struct
{
vector<int> f;
}Elem;
void split(string s, vector<int> &values) {
char sep = '\t';
int begin = 0;
for(int i = 0; i < s.size(); i++) {
if(s[i] == sep) {
int num = 0;
for(int j = begin; j < i; j++) {
num = num * 10 + s[j] - '0';
}
if(num > 0)
values.push_back(num);
begin = i + 1;
}
}
int num = 0;
for(int j = begin; j < s.size(); j++) {
num = num * 10 + s[j] - '0';
}
if(num > 0)
values.push_back(num);
}
double getPrior(vector<Elem> &data, int label) {
int good = 0;
for(int i = 0; i < data.size(); i++) {
if(data[i].f[2] == label)
good++;
}
if(good == 0)
return 0.0;
return 1.0*good/data.size();
}
double getPosterior(vector<Elem> &data, int idx, int k, int label) {
int cnt = 0;
int sum = 0;
for(int i = 0; i < data.size(); i++) {
if(data[i].f[2] == label) {
sum++;
if(data[i].f[idx] == k)
cnt++;
}
}
if(cnt == 0)
return 0.0;
return 1.0*cnt/sum;
}
int main()
{
char s[100];
vector<int> titles;
gets(s);
string strLine(s);
split(strLine, titles);
gets(s);
vector<Elem> data;
Elem item;
item.f.resize(3);
while(cin >> item.f[0] >> item.f[1] >> item.f[2]) {
data.push_back(item);
}
int i = 0;
double prior1 = getPrior(data, 1);
double prior2 = getPrior(data, 2);
vector<double> posterior1_0(11,0.0);
vector<double> posterior1_1(11,0.0);
vector<double> posterior2_0(11,0.0);
vector<double> posterior2_1(11,0.0);
int k;
for(k = 1; k <= 10; k++) {
posterior1_0[k] = getPosterior(data, 0, k, 1);
posterior1_1[k] = getPosterior(data, 1, k, 1);
posterior2_0[k] = getPosterior(data, 0, k, 2);
posterior2_1[k] = getPosterior(data, 1, k, 2);
}
for( i = 0; i < titles.size(); i++) {
if(titles[i] == 1) {
printf("%.3f\n",prior2);
} else if(titles[i] == 2) {
for(int j = 1; j <= 9; j++) {
printf("%.3lf,",posterior2_0[j]);
}
double ans = 0.0;
ans = posterior2_0[10];
printf("%.3lf\n",ans);
} else if(titles[i] == 3) {
double ans = 0.0;
ans = prior2 * posterior2_0[9] * posterior2_1[9];
ans /= (prior1 * posterior1_0[9] * posterior1_1[9] + prior2 * posterior2_0[9] * posterior2_1[9]);
printf("%.3lf\n",ans);
}
}
return 0;
}