4月16号美团点评笔试
这次笔试比前两天阿里的感觉简单一点。一个半小时5题全部ac。
给大家分享一下解法吧。
第一题,给n个学生的m科成绩,如果一个学生的某一科是单科最高,那么这个学生获得奖励,即便该学生有多科最高,也只获得一次奖励。求获得奖励的学生人数。
思路:直接遍历每个科目求最大值就行了,要注意的细节是,可能有很多学生获得该科最高分,此时所有最高分都有奖励。
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> data(n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
int tmp;
cin >> tmp;
data[i].push_back(tmp);
}
}
vector<int> flag(n);
for (int j = 0; j < m; ++j) {
int max_value = 0;
vector<int> max_index;
for (int i = 0; i < n; ++i) {
if (data[i][j] > max_value) {
max_value = data[i][j];
max_index.clear();
max_index.push_back(i);
} else if (data[i][j] == max_value) {
max_index.push_back(i);
}
}
for (int i = 0; i < max_index.size(); ++i) {
flag[max_index[i]] = 1;
}
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += flag[i];
}
cout << ans << endl;
return 0;
} 第二题,给a,b,x,m四个数,给定递推式 x=(a*x+b)%m,这个x不停算会循环,问递推第几次起x开始循环。
思路:因为取模m,x取值最多m种,维护一个m长度的flag数组,见到没见过的x就标记,如果见过了就代表循环了。
(根据抽屉原理,最多m+1次就一定会开始循环)
#include <iostream>
#include <vector>
using namespace std;
int main() {
long long a, b, x, m;
int cnt = 0;
cin >> a >> b >> m >> x;
vector<int> flag(m);
while (true) {
x = (a * x + b) % m;
if (flag[x] == 1) {
cout << cnt << endl;
break;
}
flag[x] = 1;
++cnt;
}
return 0;
}
第三题,给定n个数,这n个数两两结合构成一共n^2个有序数对(i, j)(可以自己和自己构成有序数对)。给定k,求第k大的有序数对是哪一个。
思路:先排序得到数列data,有序数对中第一个数越小那么有序数对越小。第一想法很容易想到 (data[(k-1)/n], data[(k-1)%n])是答案,但这样忽略了数列中可能存在相同元素。(这样写答案大概得64%)
例如给定1,2,2那么9个有序数对应该是:
(1,1)(1,2)(1,2)
(2,1)(2,2)(2,2)
(2,1)(2,2)(2,2)
这是第5大的应该是(2,1)而不是第二行第二列的(2,2)
这时候先定位第k大所在的有序数对中第一个数是什么,很明显是data[(k-1)/n]
再数这个数重复出现了几次,假如出现了p次。再数比这个数小的有几个,假如有q个。
那么答案就是(data[(k-1)/n], data[(k-n*q-1)/p])
这里下标想不清楚的,拿个例子对一下会比较清楚。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int n;
long long k;
vector<int> data;
cin >> n >> k;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
data.push_back(tmp);
}
sort(data.begin(), data.end());
long long a = (k - 1) / n;
long long l = 0, row = 0;
while (data[l] != data[a]) ++l;
while (data[l+row] == data[a]) ++row;
long long aa = (k - l * n - 1) / row;
printf("(%d,%d)", data[a], data[aa]);
return 0;
} 第四题,给定有序数列伪中位数的定义,m=a[floor((n-1)/2)],其实就是当n为奇数时就是正常中位数,n是偶数时取中间两个数较小的那个数。
给定一个数列,和一个值k,问至少添加几个数,使得该序列的伪中位数等于k。
思路:稍微分析就可以知道,如果需要往数列里加数,不停加k就行了,现在求需要加几个k。
先遍历数列,统计出比k小的数small个,跟k一样大的数equal个,比k大的数big个。
很容易发现,如果k是伪中位数,就一定有
small+equal >= big
big + equal > small
两个式子同时成立
第二个式子可以变换成 big + equal >= small+1
最后解出equal满足
equal >= big-small 且 equal >= small-big+1
所以equal比二者较大值大,即equal>=max(big-small, small-big+1)
需要补的k的个数即max(big-small, small-big+1) - equal
注意上式可能为负,跟0再取个最大即可。
#include <iostream>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
int cnt_big = 0, cnt_small = 0, cnt_equal = 0;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
if (tmp < k) ++cnt_small;
else if (tmp > k) ++cnt_big;
else ++cnt_equal;
}
cout << max(max(cnt_small - cnt_big + 1, cnt_big - cnt_small) - cnt_equal, 0);
return 0;
} 第五题
给两个字符串s,t求s的子串与t的子序列相同的情况有多少种。
字串的定义是连续的子字符串。
子序列的定义是不必连续的子序列。abcde的子序列可以是ace。
动态规划,定义f[i][j]为字符串s到第i个位置为止,且用上了第i个位置的字符;字符串t到第j个位置为止;此时的相同数目。(描述有点绕,不知讲清楚没有。)
状态转移方程,分两种情况:
第一种是s串的子串就是s[i]自己,(长度为1的字串)
那么此时种类数是cntj[s[i]],这个数的意思是字符串t的前j个字符里,有多少个s[i]。
这种情况很好理解。
第二种是s串字串长度大于2,且包含s[i]。
这种情况下考虑s[i]和t中的哪一个字符配对。事实上总共有cntj[s[i]]种配对方法(这个数的意思详见上面)。假设这些s[i]分别出现在t串的p1,p2,p3,...,pc (c=cntj[s[i]])这些位置
那么种类数是f[i-1][p1-1]+f[i-1][p2-1]+...+f[i-1][pc-1]
综上,f[i][j] = cntj[s[i]] + (f[i-1][p1-1]+f[i-1][p2-1]+...+f[i-1][pc-1])
最终答案为f[1][nt] + f[2][nt] + ... + f[ns][nt] ns和nt为s串和t串的长度。
另外发现这样递推复杂度还是太高。
cntj[s[i]]可以跟随着迭代更新。
(f[i-1][p1-1]+f[i-1][p2-1]+...+f[i-1][pc-1])这一串也可以跟着迭代更新,这是下面代码中value的意义。
下面代码中value[ch][i]表示假设ch分别出现在t串的p1,p2,p3,...,pc (c=cntj[ch])这些位置,(f[i][p1-1]+f[i][p2-1]+...+f[i][pc-1])的值。
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
const int M = 1000000007;
int f[5001][5001], ns, nt;
string s, t;
int main() {
cin >> s >> t;
ns = s.size();
nt = t.size();
unordered_map<char, int> cnt;
unordered_map<char, vector<int>> value;
for (int j = 1; j <= nt; ++j) {
char tt = t[j-1];
cnt[tt]++;
if (value.find(tt) == value.end()) {
value[tt].resize(ns+1);
}
for (int i = 1; i <= ns; ++i) {
char ss = s[i-1];
f[i][j] = cnt[ss];
if (value.find(ss) != value.end()) {
f[i][j] = (f[i][j] + value[ss][i-1]) % M;
}
value[tt][i] = (value[tt][i] + f[i][j-1]) % M;
}
}
int ans = 0;
for (int i = 1; i <= ns; ++i) {
ans = (ans + f[i][nt]) % M;
}
cout << ans;
return 0;
}
查看22道真题和解析