好多天前做的头条,今日补上博客。
第一题
题意
在n个元素的数组中,找到差值为k的数字对去重后的个数。
输入描述:
第一行包含两个正整数,n和k,n表示数字个数,k表示差值。
第二行,n个正整数输出描述:
差值为k的数字对去重后的个数
输入
5 2
1 5 3 4 2
5 0
1 1 1 2 2
输出
3
2
说明
第一个样例,数字对分别为(1,3),(5,3),(4,2)
第二个样例,数字对分别为(1,1),(2,2)
思路
分两种情况讨论:
1、k大于0,此时的重复对肯定不是(1,1),(2,2),所以对所有的数排序,然后去掉重复的数字。遍历所有数字,对每个数字都再遍历其之后的数字,如果有满足差值为k的数字,或者遍历到差值已经大于k的数字,都不用再遍历其之后的数字(节省时间)。
2、k为0,此时的重复对便形如(1,1),(2,2),所以不能再去重了。仍然对所有的数排序,然后遍历所有数字,如果找到某一个数的后一个数刚好与其相等,说明找到题意所求,记得让遍历的索引跳过这个数之后的所有与这个数相等的元素。
代码
#include <vector>
#include <algorithm>
#include <cstdio>
using namespace std;
int main(void) {
int num, key, i, j;
scanf("%d%d", &num, &key);
vector<int> a;
vector<int>::iterator it1;
vector<int>::iterator it2;
// 记录答案
int count = 0;
for(i = 0; i < num; i++) {
scanf("%d", &j);
a.push_back(j);
}
sort(a.begin(), a.end());
// key不为0,则大胆去重后双重循环找
if (key != 0) {
a.erase(unique(a.begin(), a.end()), a.end());
for(it1 = a.begin(); it1 != a.end(); it1++) {
for(it2 = it1 + 1; it2 != a.end(); it2++) {
if(*it2 - *it1 == key) {
// 找到立即break节省时间
count++;
break;
}
if(*it2 - *it1 > key) {
// 如果超过key,后面也不可能有了,break
break;
}
}
}
} else {
// key为0,则一重循环找即可。
for(it1 = a.begin(); it1 != a.end(); it1++) {
it2 = it1 + 1;
if (*it2 == *it1) {
count++;
while (it1 != a.end() && *it1 == *it2) {
// 跳过后面相等的数字
it1++;
}
it1--;
}
}
}
printf("%d\n", count);
}
代码通过样例100%
第二题
题意
定义两个字符串变量:s和m,再定义两种操作
第一种操作:
m = s;
s = s + s;
第二种操作:
s = s + m;
假设s,m初始化如下:
s = "a";
m = s;
求最小的操作步骤数,可以将s拼接到长度等于n。输入描述:
一个整数n,表明我们需要得到s字符串长度
对于100%的数据,0
输入
6
输出
3
说明
两次第一种操作+一次第二种操作
思路
每次操作都是同样的两手选择,这种重复性可以用递归去模拟。这种类似枚举的过程,能让我们把所有能让s拼接到长度为n的操作都模拟出来,维护一个全局变量,每次模拟出就取最小值即可。长度为n是一种递归结束条件,另一种递归结束条件是长度大于n,因为之后怎么操作也会大于n。
代码
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
int n;
int res;
void cal(string s, string m, int sum) {
if (s.length() > n) return;
if (s.length() == n) {
res = min(res, sum);
return;
}
// 第一种操作
string newm = s;
string news1 = s + s;
cal(news1, newm, sum+1);
// 第二种操作
string news2 = s + m;
cal(news2, m, sum+1);
}
int main(void) {
cin >> n;
// res最大值为n-1,因为可以用n-1次第二种操作
res = n-1;
string s = "a";
string m = s;
// 第三个参数代表操作数
cal(s, m, 0);
cout << res << endl;
}
第三题——纸牌游戏
题意
读取一个表达式,输出以下格式表示的计算结果。
如下是”1234567890”
输入描述:
第一行为一个正整数n
接下来n行每一行为一个表达式输出描述:
输出字符’6’拼成的计算结果
输入
2
6+6
6*6
输出
思路
首先解决计算表达式的问题。样例只有加减乘(然而题意没说,所以真的很坑)。加减乘的表达式可以理解为A*a+B*b+C*c,这里的大写字母都是权值(系数),小写字母是真实数字。比如:
1+9则可以当做0+1*1+1*9(此时两个权值都为1)。
6-5+2*24则可以当做0+1*6-1*5+2*24(此时三个权值分别为1,-1,2)。
一个表达式的计算结果从0开始,不断“+权值*数字”。理解到这就知道怎么计算表达式了,维护三个变量,一个cur为当前数字的值,一个prd为当前数字的权值,一个sum为当前数字前的表达式的总和。于是,每识别完一个数字就sum+=prd*cur即可。
现在有了表达式计算结果,再不断取各位上的数字出来。维护一个大数字的三维数组,根据取出的位数,取出对应行列位置的字符串,循环五次绘制整个数字(因为一整个大数字有五行)
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
char big[5][10][6] = {
{"66666", "....6", "66666", "66666", "6...6", "66666", "66666", "66666", "66666", "66666"},
{"6...6", "....6", "....6", "....6", "6...6", "6....", "6....", "....6", "6...6", "6...6"},
{"6...6", "....6", "66666", "66666", "66666", "66666", "66666", "....6", "66666", "66666"},
{"6...6", "....6", "6....", "....6", "....6", "....6", "6...6", "....6", "6...6", "....6"},
{"66666", "....6", "66666", "66666", "....6", "66666", "66666", "....6", "66666", "66666"}
};
long long cal(char s[]) {
int n = strlen(s);
long long sum = 0, cur = 0, prd = 1;
for (int i = 0; i < n; ++i) {
if (s[i] >= '0' && s[i] <= '9') {
cur = cur*10+s[i]-'0';
} else if(s[i] == '+') {
sum += prd*cur;
cur = 0;
prd = 1;
} else if (s[i] == '-') {
sum += prd*cur;
cur = 0;
prd = -1;
} else if (s[i] == '*') {
prd *= cur;
cur = 0;
}
}
sum += prd*cur;
return s;
}
int main(void) {
int n;
scanf("%d", &n);
while (n--) {
char s[100];
scanf("%s", s);
// 计算表达式结果
long long ans = cal(s);
// 取各位上的数装进vector
vector<int> v;
long long tmp = ans;
while (tmp) {
int wei = tmp % 10;
v.push_back(wei);
tmp /= 10;
}
reverse(v.begin(), v.end());
// 记得还有结果为0的情况
if (v.empty()) {
v.push_back(0);
}
for (int i = 0; i < 5; ++i) {
// i代表当前要画第几行,因为大数字总共有五行
for (int j = 0; j < v.size(); ++j) {
// 判断要画哪个数字
int num = v[j];
printf("%s%s", big[i][num], j+1==v.size() ? "\n" : "..");
}
}
}
return 0;
}
第四题
题意
给一个包含n个整数元素的集合a,一个包含m个整数元素的集合b。
定义magic操作为,从一个集合中取出一个元素,放到另一个集合里,且操作过后每个集合的平均值都大于操作前。
注意以下两点:
①不可以把一个集合的元素取空,这样就没有平均值了
②值为x的元素从集合b取出放入集合a,但集合a中已经有值为x的元素,则a的平均值不变(因为集合元素不会重复),b的平均值可能会改变(因为x被取出了)
问最多可以进行多少次的magic操作?输入描述:
第一行为两个整数n,m
第二行n个整数,表示集合a中的元素
第三行m个整数,表示集合b中的元素
对于30%的数据,最终结果<=1
对于70%的数据,输入中的a,b集合元素完全没有重复,即|a|+|b|=|aUb|
对于100%的数据,1<n,m<100,000,0<a[i],b[i]<100,000,000,集合a中元素互不相同,集合b中元素互不相同。输出描述
输出一个整数,表示最多可以进行的操作次数。
输入
3 5
1 2 5
2 3 4 5 6
输出
2
说明
从集合b中取出3,4元素放入集合a中
思路
假设A的均值比B的均值更大,操作次数要尽可能地多,那么最贪心的做法是从A里取一个最小值元素丢到B里了,这样A的均值涨的比较快,可以取的就更多了,而B的平均值涨的比较慢,可以丢的就更多了。
然后一直从A里取越来越大的值(不超过A的均值,不然A的均值没法涨)丢到B里。注意,这个丢的过程中A、B的平均值都会涨。而且A也不是所有元素都可以丢到B里的,只能从A里取出现次数等于1的数(不然A的均值不涨),而且这个数在B里不能出现过(不然B的均值不涨)。
综上所有条件,A取越来越大的元素,所以要对A进行排序。只能取出现次数为1的数,所以要对A去重。因此考虑用set容器。
代码
#include <bits/stdc++.h>
using namespace std;
const long double eps = 1e-14;
int cmp(long double a, long double b) {
if (fabs(a-b) <= eps) return 0;
return a > b ? 1 : -1;
}
long double chu(long long k, int m) {
return (long double)k/m;
}
int main(void) {
int n, m;
scanf("%d%d", &n, &m);
set<int> numA, numB;
long long sumA = 0, sumB = 0;
for (int i = 0; i < n; ++i) {
int tmp;
scanf("%d", &tmp);
numA.insert(tmp);
sumA += tmp;
}
for (int i = 0; i < m; ++i) {
int tmp;
scanf("%d", &tmp);
numB.insert(tmp);
sumB += tmp;
}
// 保证numA的均值是大于numB的均值的,如果不是这样则交换AB
if (cmp(chu(sumA, n), chu(sumB, m)) == -1) {
swap(sumA, sumB);
swap(n, m);
numA.swap(numB);
}
int res = 0;
// 按小到大顺序,不断取A的元素
for (set<int>::iterator it = numA.begin(); it != numA.end(); it++) {
int k = *it;
// A是有序的,如果当前元素大于等于A的均值,则不用再取了
if (cmp(k, chu(sumA, n)) >= 0)
break;
if (!numB.count(k) && cmp(k, chu(sumB, m)) > 0) {
// 放到B的条件是要大于B的均值并且B没有这个数
++res;
sumB += k;
++m;
sumA -= k;
--n;
}
}
printf("%d\n", res);
return 0;
}
第五题
题意
小T最近迷上了一款跳板小游戏。
已知空中有N个高度互不相同的跳板,小T刚开始在高度为0的地方,每次跳跃可以选择与自己当前高度绝对值差小于等于H的跳板,跳跃过后到达以跳板为轴的镜像位置,问小T在最多跳K次的情况下最高能跳多高?(任意时刻,高度不能为负)输入描述:
三个整数,N,K,H
以下N行,每行一个整数T[i],表示第i个跳板的离地高度。输出描述:
一个整数,表示最高能跳到的高度
输入
3 3 2
1
3
6
输出
8
说明
第一次跳跃,选择高度为1的跳板,结束后到达高度为2的地方。0+(1-0)*2=2,所以结束后到高度为2的地方(注意是以跳板为轴的镜像位置)
计算公式:结束高度 = 初始高度 + (所选跳板高度-初始高度)*2
思路
初始位置是0,先把所有的板的位置记录下来,然后用BFS,将位置小于(0+可跳高度H)的所有跳板都跳个遍,记录所有能到达的位置,然后对这些到达的位置再重复同样的操作(所有记录的位置都会跟保存最终结果的变量比较大小),这期间再维护一个变量记录跳到该位置时耗费的跳的次数(用来与K比较,而又要记录位置又要记录跳的次数,可以考虑用pair),一直到跳的次数到达K或者没跳板可以跳了再终止BFS。
然而这样有个问题,比如板有5,9,11,13,H为5,跳跃的次数无穷大,那么不断往上跳可以从
0→(通过5)→10→(通过13)→16
然而其实最远的是
0→(通过5)→10→(通过9)→8→(通过13)→18
也就是,最完美的情况是(最高跳板的位置+H)。而有可能因为我们离最高跳板比较近,导致能跳的距离小于H,就达不到最远距离了。这时候如果能往后跳一下,再跳最高的那块跳板就能跳得更远了,因此BFS的时候不仅要将往上的所有能跳的跳板都跳了,还要往下将所有能跳的跳板也跳了。
代码
#include <bits/stdc++.h>
using namespace std;
bool Postion[250000];
bool Springboard[250000];
int main(void) {
int n, k, h;
scanf("%d%d%d", &n, &k, &h);
for (int i = 0; i < n; ++i) {
int tmp;
scanf("%d", &tmp);
Springboard[tmp] = true;
}
// BFS,pair第一个表位置,第二个表已跳跃次数
queue<pair<int, int> > q;
q.push(make_pair(0, 0));
int res = 0;
while (!q.empty()) {
pair<int, int> p = q.front();
q.pop();
// BFS,次数超过k,后面的也肯定超过K,不用看了
if(p.second>k) break;
res = max(res, p.first);
for (int i = 1; i <= h; ++i) {
if (Springboard[p.first + i] && !Postion[p.first+2*i]) {
Postion[p.first+2*i] = true;
q.push(make_pair(p.first+2*i, p.second+1));
}
if (p.first-2*i > 0 && Springboard[p.first-i] && !Postion[p.first-2*i]) {
Postion[p.first-2*i] = true;
q.push(make_pair(p.first-2*i, p.second+1));
}
}
}
printf("%d\n", res);
return 0;
}
总结
不得不说今日头条一下五道编程题还是很恐怖的,比其他厂的笔试题要难很多。