腾讯笔试 2023-09-15
T1
一堆数链,这些数链里面的数字都杂乱无章,你需要整理一下这些数字,把它们从小到大排成一个数链。
解法:放到一个 vector 里面,排序,翻转,头插一波带走。
T2
长度为 n 的数字,每次选择其中一个数字:
- 如果是奇数,那么奇数乘以 2
- 如果是偶数,那么偶数乘以 2 再加 1
如果进行 k 次操作,那么操作之后数组元素之和最小是多少?
解法:用最小堆维护这些元素,每次选择最小的去操作即可。
T3
n 个赛车,第 i 个赛车的位置为,速度为
,匀速向前行驶,满足
。问 t 秒后,有多少车的排名上升了?
如果两辆车的位置相同,则认为排名相同。一辆车的排名为在它前面的车辆个数加一。
解法:首先标记每个赛车当前的排名,然后再计算出 t 秒后该车的位置。排序后,用当前排名与之前的排名做比较即可。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, t;
cin >> n >> t;
vector<int> p(n), v(n), p2(n), idx(n);
for (int i = 0; i < n; i++) {
cin >> p[i];
idx[i] = n - i - 1; // 将排名规定为 0~n-1
}
for (int i = 0; i < n; i++) {
cin >> v[i];
}
// 三个数组翻转,前面的 p 值更大,排名数字更小
reverse(p.begin(), p.end());
reverse(v.begin(), v.end());
reverse(idx.begin(), idx.end());
for (int i = 0; i < n; i++) {
p2[i] = p[i] + v[i] * t;
}
// 对 idx 排序,依据为对应下标 p2 的值
sort(idx.begin(), idx.end(), [&](int x, int y) {
return p2[x] > p2[y];
});
int res = 0;
int cur = 1;
for (int i = 0; i < n; i++) {
if (i > 0 && p2[idx[i]] == p2[idx[i - 1]]) {
cur = cur;
} else {
cur = i + 1;
}
if (cur < idx[i] + 1) {
res++;
}
}
cout << res << endl;
}
T4
对于一个字符串,如果把字符串的第一个字符放到最后,得到的新串就是原来字符串的旋转串。
一个字符串的旋转串的旋转串也是这个字符串的旋转串。即这种关系具有传递性。即 abc 的旋转串有 bca,cab,abc 等。
如果存在一个字符串,既是 x 的旋转串,也是 y 的旋转串,那么我们称 x 和 y 匹配。问 每一组输入中是否有两个字符串匹配。
输入总共有 T 组(T 小于等于 50),每组最多 5000 个长度不超过 50 的字符串。
解法:最小表示法 + 哈希。
字符串的最小表示法就是,在所有通过旋转操作可以转换得到的字符串中,字典序最小的那一个。
通过 O(n) 的时间计算每个字符串的最小表示法,然后使用哈希判断是否有重复的即可。
复杂度 O(Tnm),其中 m 为字符串的长度。
#include <bits/stdc++.h>
using namespace std;
string min_express(string s) {
int n = s.size();
int i = 0, j = 1, k = 0;
while (k < n && i < n && j < n) {
if (s[(i + k) % n] == s[(j + k) % n]) k++;
else {
s[(i + k) % n] > s[(j + k) % n] ? i += k + 1 : j += k + 1;
if (i == j) i++;
k = 0;
}
}
i = min(i, j);
return s.substr(i) + s.substr(0, i);
}
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
unordered_set<string> st;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
st.insert(min_express(s));
}
if (st.size() == n) cout << "NO" << "\n";
else cout << "YES" << "\n";
}
}
T5
有 n 个点在一维数轴上,第 i 个点的坐标为 xi,颜色为 ci。ci 为 0 表示红色,为 1 表示蓝色。
每次你可以选择:
- 选择一个红点,将它向左或者向右移动,即 x 变成 x-1 或者 x+1。
- 选择一个蓝点,将它变成红点。
最多可以进行 k 次操作 2,问最少要进行多少次操作 1 可以使得任意两个红点之间不存在蓝点。
数据范围:
解法:蓝色最终只能分布在两边,枚举右边蓝色中坐标最小的那个位置为 i,找到最大的 j 满足:第 j + 1 到 i - 1 个点中最多只能有 k 个蓝色,这些都必须使用操作 2 将它们变成红色。
此时,如果 i != j,并且 x[j] + 1 < x[i],那么就可以把左右两边的红色挪到中间的空位上。左边的统一挪到 x[j] + 1,右边的挪到 x[i] - 1。
维护双指针,并且维护左边和右边红点的坐标之和和个数,方便计算代价。
注意全局没有蓝点的情况,以及可以把所有的红点挪到最后一个蓝点右边的情况。我们可以在末尾加一个虚拟蓝点,做统一处理。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int n, k;
cin >> n >> k;
vector<int> x(n + 1), c(n + 1);
for (int i = 0; i < n; i++) cin >> x[i];
for (int j = 0; j < n; j++) cin >> c[j];
// 左右两边红点的坐标之和和个数
ll Lsum = 0, Lcnt = 0, Rsum = 0, Rcnt = 0;
for (int i = 0; i < n; i++) {
if (c[i] == 0) {
Rsum += x[i];
Rcnt++;
}
}
// Bcnt 表示双指针区间内蓝点的个数,如果大于 k 就持续往右移动 j
int Bcnt = 0;
ll res = 1e18;
// 添加虚拟蓝点
x[n] = 2e9;
c[n] = 1;
n++;
for (int i = 0, j = -1; i < n; i++) {
if (c[i] == 0) {
Rsum -= x[i];
Rcnt--;
} else {
while (j < i && Bcnt > k) {
if (c[j + 1] == 0) {
Lsum += x[j + 1];
Lcnt++;
} else {
Bcnt--;
}
j++;
}
if (i != j && x[i] > x[j] + 1) {
res = min(res, Lcnt * (x[j] + 1) - Lsum + Rsum - Rcnt * (x[i] - 1));
}
Bcnt++;
}
}
cout << res << endl;
return 0;
}
总体难度不简单,易错点和坑点很多,很值得复盘的一套题目。赛中大概花了 50min AK,因为没有罚时所以写的时候很随意,胡乱 WA 了很多发。
#笔试##腾讯笔试#
