2024届服务端研发实习生笔试1--拼多多

题1

多多君最近在研究种编码算法:对于连续出现的字符,用变动的长度来表示,发现这样可以有效的减少存储的空间。
例如原字符串为:"AAAABBBCCDEEEE",2个C由4个A、3个B、1个D、4个E组成,编码后字符串为:“4A3B2C1D4E“,那么存储空间从14变成了10。多多君想测试这种算法的实际效果,于是找到了你,请你帮忙实现这种算法的解码过程。

输入描述

共一行,一个字符串,表示需要进行解码的字符串,字符串由大小与字母纽成。(1<=字符串长度<=500)

输出描述

共一行,一个字符串,表示字符串解码的结果。

示例1

输入

10a1b1c

输出

aaaaaaaaaabc

示例2

输入

1P2D1p2d1P1D1d

输出

PDDpddPDd

备注

输入数据保证字符串均为合法编码后的字符串

思路:属于是试水简单题,设定变量num,num表示对应出现的次数,根据题意每一个最小的部分构成一定是 出现次数+出现字符,因此每次遇到字符输出 num个对应的字符即可。

#include<iostream>
#include<string>
using namespace std;
int main(){
	string encoded_str;
	cin >> encoded_str;
	int num = 0;
	for(char c:encoded_str){
		if(c >= '0' && c <= '9'){
			num = num * 10 + c - '0';
		}else{
			if(num){
				cout << string(num, c);
			}
			num = 0;
		}
	} 
	return 0;
}

 题2

多多最近下载了一款飞机大战的游戏,多多可以通过游戏上的不同发射按键来控制飞机发射子弹:
按下A键,飞机会发射出2枚子弹,每个子弹会对命中的敌人造成1点固定伤害,但不能作用于同一个敌人。
按下B键,飞机会发射出1枚子弹,子弹会对命中的敌人造成巨额伤害并瞬间将其秒杀。
多多是一个游戏高手,总是能操控子弹命中想要命中的敌人。这个游戏一共有T个关卡,消灭消灭当前关卡全部敌人后,发射出去多余的子弹会小时,游戏会自动进入下一个关卡。
假设每个关卡都会在屏幕中同时出现N个敌人,这N个敌人所能承受的伤害也已经知道。多多想知道,每个关卡自己最少按几次发射按键就可以将敌人全部消灭?

输入描述

第一行输入一个固定数字T(1<=T<=1000)表示关卡的总数量,N(1<=N<=2000)表示每个关卡出现的敌人数量。
接下来T行,每行有N个数字D1,D2,.....DN(1<=Di<=200),分别表示这N个敌人所能承受的伤害。

输出描述

结果共有N行,每行一个数字,分别表示对于这个关卡,最少按几次发射按键就可以将敌人全部消灭。

示例1

输入输出示例仅供调试,后台判题数据一般不包含示例。

输入

3 3
1 2 1
2 3 2
1 2 3

输出

2
3
3

说明

游戏共有3个关卡,每个关卡会出现3个敌人。

思路:分析一下,一次选择发射一个子弹秒杀一个敌人和两个子弹对两个敌人造成1点伤害,从效率上来说,一次发射两个子弹只有在两个敌人均为1点生命值时效率才会高于一发子弹秒杀一个敌人(在这种情况下,一次发射两个子弹一次就可以消灭两个生命值为1的敌人,而秒杀弹则需要两次)。换种理解方式,一次发射两个子弹造成的伤害总量只有2,在单体生命值大于等于2时,对上秒杀没有优势。

所以我们其实只需要统计一下1个个数,对于1的消灭,我们尽量采取两两同时消的策略,而对于剩余的大于1的部分,我们直接秒杀即可,如果1的个数为奇数个,我们也没有必要采用一次发射两个子弹,举例:余下1个1,与其他大于1的数,不妨设最小值2(因为值越小,秒杀的性价比越低),此时我们如果采用一次两颗子弹,那么就会变成0、1,此时想要清理完,还是需要一次秒杀,其实和两次秒杀(秒杀1和2)是一样的步数。而如果这个剩下的1继续和其他更大的值一起消,也只是重复该过程,最极端的情况就是 1 2 2 2 2 ... 2 2 2。

不失一般性,我们就假设有5个2,即 1 2 2 2 2 2,那么使用两个子弹消去的策略,过程为:

0 1 2 2 2 2
0 0 1 2 2 2
0 0 0 1 2 2
0 0 0 0 1 2
0 0 0 0 0 1
0 0 0 0 0 0

可以看到依旧是6步。而如果值比2大一点,则两发子弹就完全没有优势了。

#include<iostream>
using namespace std;

int main(){
	int T, N, idx, value, one_count, not_one_count;
	cin >> T >> N;
	while(T -- ){
		one_count = not_one_count = 0;
		for(idx = 0; idx < N; ++ idx){
			cin >> value;
			if(value == 1){
				++ one_count;
			} else{
				++ not_one_count;
			}
		}
		cout << (one_count / 2) + (one_count % 2) + not_one_count << endl;
	}
    return 0;
}

题3

多多君开了一家自助餐厅,为了更好地管理库存,多多君每天需要对之前的客流量数据进行分析,并根据客流量的平均数和中位数来制定合理的备货策略。

输入描述

输入共两行:

第一行一个整数N,表示餐厅营业总天数(0<N<=200,000) ,

第二行共N个整数,分别表示第i天的客流量\small R_i(0<=R_i<=1,000,000) 。

输出描述

第一行长度为N,其中第i个值表示前i天客流量的平均值,

第二行长度为N,其中第i个值表示前i天客流量的中位数。

示例1

输入输出示例仅供调试,后台判题数据一般不包含示例

输入

5
1 2 3 4 10

输出

1 2 2 3 4
1 2 2 3 3

说明

输出结果四舍五入为整数

备注

1.输出结果四舍五入为整数;

思路:对于平均值,我们只需要存储流量和求平均数即可,对于四舍五入,我们只需要对结果进行+0.5后取整即可。而输出中位数,当我们看了前i个数的时候,如果前i个数已经满足有序,那么只需要根据i的奇偶性来确定是取中间的一个数还是取中间两个数的平均值。而对于有序部分的维护,我们可以用插入排序来完成,复杂度为O(n^2)。

优化:用插排去维护的复杂度也很高,对于中位数问题,其实就是一个经典的数据流取中位数的问题,我们需要用大小堆来解决,现在有i个数,我们想求中位数,只要能够将较大的一半数放在小根堆里,将较小的一半放在大根堆里,这样可以保证小根堆堆顶是更大那一部分数的最小值,而大根堆堆顶是更小那一部分数的最大值。

在实际实现的时候,为了方便,当两个堆大小一样(包括都为空)的时候,我们就默认插到大根堆里(较小的那一半),如果发现大根堆的堆顶比小根堆的堆顶大,此时需要置换两个堆顶。

当两个堆大小不一样(为了取到中位数,我们必须保证两个堆大小不会大于1,同时因为我们默认会插大根堆,因此我们整体上应该满足大根堆的大小不会比小根堆大超过1),如果当前要入堆的数小于大根堆顶,即他是较小的一半,则这个数必须要入大根堆,且原大根堆顶必须弹出并入小根堆。如果当前要入堆的数大于等于大根堆顶,则直接入小根堆即可。

#include<iostream>
#include<queue>
#include<vector>
using namespace std;

const int maxn = 2e5 + 7;
int mean[maxn], middle_num[maxn];

int main(){
	int i, n, sum = 0, value, small_heap_biggest, big_heap_smallest;
	cin >> n; 
	priority_queue<int> big_root_heap;
	priority_queue<int, vector<int>, greater<int>> small_root_heap;
	for(i = 1; i <= n; ++ i){
		cin >> value;
		sum += value;
		mean[i] = int((double)sum / i + 0.5);
		if(big_root_heap.size() ==  small_root_heap.size()){// 
			big_root_heap.push(value);
			if(!small_root_heap.empty() && small_root_heap.top() < big_root_heap.top()) {
				small_heap_biggest = small_root_heap.top(); small_root_heap.pop();
				big_heap_smallest = big_root_heap.top(); big_root_heap.pop();
				big_root_heap.push(small_heap_biggest);
				small_root_heap.push(big_heap_smallest);
			}
		}else{//只可能大根堆多一个数 
			if(value < big_root_heap.top()){//而且必须要插到大根堆里去 需要置换 
				big_heap_smallest = big_root_heap.top(); big_root_heap.pop();
				small_root_heap.push(big_heap_smallest);
				big_root_heap.push(value);
			}else{
				small_root_heap.push(value);
			}
		} 
		middle_num[i] = (small_root_heap.size() == big_root_heap.size()) ? int(double(small_root_heap.top() + big_root_heap.top()) / 2 + 0.5) : big_root_heap.top();
	}
	for(i = 1; i <= n; ++ i){
		cout << mean[i] << " ";
	}
	cout << endl;
	for(i = 1; i <= n; ++ i){
		cout << middle_num[i] << " ";
	}
    return 0;
} 

题4

多多君准备了三个活动(分别编号A、B和C),每个活动分别有人数上限以及每个人参加的费用。
参加团建的有N个人(分别编号1 ~N),每个人先投票选择若干个意向的活动,最终每个人只能参加其中一个。
多多君收集完投票结果后,发现如何安排成为了大难题:如何在满足所有人的意向的情况下,使得活动的总费用最少。
于是多多君找到了擅长编程的你,希望你能帮助找到一个合理的团建计划。

输入描述

第一行,一个整数N,代表准备参加活动的人数。
(1<=N<=100)
接下来行,每行一个由"ABC"组成的字符串,其中第i行表示第i个人投票了哪几个活动。
(输入保证字符串非空,且由大写的"ABC"字符组成)
最后3行,每行两个整数,分别表示三个活动的人数上限以及每个人参加的费用。
(人数上限以及参与活动的费用均为不超过100的正整数)

输出描述

输出共2行
如果能满足所有人的要求,第一行输出"YES",第二行输出最少的总费用。
如果不能满足所有人的要求,第一行输出"NO",第二行输出最多能满足多少人。

示例1

输入输出示例仅供调试,后台判题数据股不包含示例。

输入

5
A
B
C
AB
ABC
2 1
2 2
2 3

输出

YES
9

思路:动态规划问题,定义dp[attended_A][attended_B][attended_C]表示当前A活动参加了attended_A个人,B活动参加了attended_B个人,C活动参加了attended_C。用A[i]、B[i]、C[i]表示第i个人对A、B、C的意向。

那么考虑第i个人的时候,对于dp[attended_A][attended_B][attended_C]自然要考虑这个人是参加了A、B、C中的哪一个,即有如下转移方程:

\large dp[attended\_A][attended\_B][attended\_C]=\left\{\begin{matrix} dp[attended\_A-1][attended\_B][attended\_C]+cost\_A,(attended_A <= limit\_person\_A, A[i]=true) \\ dp[attended\_A][attended\_B-1][attended\_C]+cost\_B,(attended_B <= limit\_person\_B,B[i]=true) \\ dp[attended\_A][attended\_B][attended\_C-1]++cost\_C,(attended_C <= limit\_person\_C,C[i]=true) \end{matrix}\right.

#include<iostream>
#include<string>
#include<cstring>
using namespace std;

const int maxn = 1e2 + 5;
bool A[maxn], B[maxn], C[maxn];
int dp[maxn][maxn][maxn], attended_A, attended_B, attended_C, limit_person_A, limit_person_B, limit_person_C, cost_A, cost_B, cost_C; 

int main(){
	int i, N, max_arrange_num = 0, min_cost = 0;
	string activity;
	memset(dp, 0x3f, sizeof(dp));
	cin >> N;
	for(i = 1; i <= N; ++ i){
		cin >> activity;
		for(char c : activity){
			switch(c) {
				case 'A':A[i] = true;
						break;
				case 'B': B[i] = true;
						break;
				case 'C': C[i] = true;
						break;	
			}
		}
	}
	cin >> limit_person_A >> cost_A >> limit_person_B >> cost_B >> limit_person_C >> cost_C;
	dp[0][0][0] = 0;
	for(i = 1; i <= N; ++ i){//每个人挨个考虑 
		for(attended_A = 0; attended_A <= i && attended_A <= limit_person_A; ++ attended_A){
			for(attended_B = 0; attended_A + attended_B <= i && attended_B <= limit_person_B; ++ attended_B){
				attended_C = i - attended_A - attended_B;
				if(attended_C > limit_person_C){// a和b合起来太小了 这里可以利用起来限制attended_B的最小值可以加速 
					continue;
				}
				
				if(attended_A > 0 && A[i]){
					dp[attended_A][attended_B][attended_C] = min(dp[attended_A][attended_B][attended_C], dp[attended_A - 1][attended_B][attended_C] + cost_A);
				}
				if(attended_B > 0 && B[i]){
					dp[attended_A][attended_B][attended_C] = min(dp[attended_A][attended_B][attended_C], dp[attended_A][attended_B - 1][attended_C] + cost_B);
				}
				if(attended_C > 0 && C[i]){
					dp[attended_A][attended_B][attended_C] = min(dp[attended_A][attended_B][attended_C], dp[attended_A][attended_B][attended_C - 1] + cost_C);
				}
				
				if(dp[attended_A][attended_B][attended_C] != 0x3f3f3f3f){//当前安排合理 
					if(i > max_arrange_num){//第一次更新到i个人安排好 
						max_arrange_num = i;
						min_cost = dp[attended_A][attended_B][attended_C];
					}else{
						min_cost = min(min_cost, dp[attended_A][attended_B][attended_C]);
					}
				}
			}
		}
	}
	
	if(max_arrange_num == N){
		cout << "YES" << "\n" << min_cost;
	}else{
		cout << "NO" << "\n" << max_arrange_num;
	}
	
	return 0;
} 
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值