2020年腾讯实习生算法笔试题目
参加了腾讯20年的实习生笔试,本来都不打算写这种笔试的题目。但是感觉着产生的想法很多。首先声明我不是什么大佬,下面写的内容没有得到印证的地方还是会出现偏差,希望各位指正。这里就先谈谈感想,如果笔试编程存在多个题目,而这里有的部分题目不是一下做出来,那么这场笔试就已经变成了多目标优化,而事实是往往很难找到最优点,不知道如何分配时间使你的收益最大化。所以事实就是,往往的结果不是你的收益最大化。
也许你的坚持浪费在了不该有的题目上,也许再努力一步就会有结果的题目,你却提前终止,所以结果不重要,没必要为了不是最优的结果而悔恨。下面看题目。
题目描述
有一个人需要去打怪物,每打一个怪物需要耗费xi的血量,但是会获得yi的金币,然后开局可以使用金币去购买血量,一个金币可以购买q点血量,用不完的血量所有怪物结束之后不会保留,每一个怪物都可以选择打或者不打,问最后结束时,可以获得最大的收益是多少。
输入描述
第一行输入n q,分别表示总共有n个怪物,和一个金币可以购买q点血量
接下来的n行,分别是xi yi分别是打一个怪物的消耗和金币收益输入
3 2
1 1
1 10
3 1输出
10
这个题目应该算是比较简单的一道了,如果能正确的搞清数据的关系,直接贪心算法就可以了。如果收益大于消耗就一定去打这个怪物,但是收益和消耗需要置换到同一维度,显然以血量为单位比较合适,只需要乘法。计算打一个怪物,收益的血量大于消耗的血量,打完之后,金币收益累加,消耗血量累加,最后决定买多少血量即可。
因为我的描述有提出关键信息,这一题很简单,但是有很多人因为理解错题意做错了,在考场上,这种情况比比皆是。下面看代码。
代码示例
from math import ceil
n,q= [int(i) for i in input().split(' ')]
res = 0
b = 0
for i in range(n):
cost,gain = [int(i) for i in input().split(' ')]
if gain *q >cost:
res += gain
b += cost
print(res - ceil(b/q))
题目描述
求函数y2=2Ax和y=Bx+C两个曲线所围出的面积。如果没有所围面积,则输出0
输入描述
第一行一个数n,表示测试用例的组数
接下来n行,每行输入三个数,分别是A B C
每个用例输出一个数,表示面积,相对误差在10−4都算对输入
1
1 1 -6输出
31.2481110540(大概是这样,反正这样是对的)
每一个学过高等数学的同学都知道这个题要用定积分处理,所以肯定是先求交点,可能没有交点,此时所围面积就是0了。第一个函数是y的二次函数,看起来不方便,把x和y交换,然后联立方程求交点。y=2Ax2=Bx−C,就能得到二次函数,最后化简得到判别式>0则两个交点,存在面积,否则不存在面积。
到求交点都没什么问题,但是后面的操作就有点让我觉得自己是个憨憨。学过数值分析的同学肯定就知道计算机怎么求积分,把一个函数切分成很多矩形或者梯形,然后把矩形(梯形)的面积累加。对了我就这样写了,不超时才怪。
正经求法应该是要对这个二次函数手动求定积分,求出来是个三次函数,然后带入上下限进行计算,别人是这样过了的。具体细节就不展开了,直接上代码了。
代码示例
n = int(input())
for i in range(n):
A,B,C = [int(i) for i in input().split(' ')]
delt = 4*A**2/(B**2) - (8*A*C/B)
if delt <= 0:
print(0)
else:
x1,x2 = ((2*A/B) - delt**0.5)*0.5 , ((2*A/B) + delt**0.5)*0.5
print ((x1**3 / (6*A) - x1**2 /(2*B) + x1*C/B) - x2**3 / (6*A) + x2**2 / (2*B) - x2*C/B)
题目描述
一个*有n个房子,每个房子的人可以选择一个1~m的数,如果相邻房子的人选择的数字是一样的,就会发生冲突,问发生冲突的可能性有多少种。
输入,m n分别表示可选数字的范围和房间的数目。输出冲突的种类,并对100003取模
输入
2 3输出
6
对于样例输入,发生冲突的情况分别是(1,1,1),(1,1,2),(1,2,2),(2,1,1),(2,2,1),(2,2,2)
这个问题第一遍没想出来是错的,后面想到了可以考虑总的排列数目减去不发生冲突的数目。总的排列数目就是mn,然后不发生冲突,第一个人有m个选择,第二个人只需要不和第一个人冲突即可,有m-1种选择,后一个人只需要保证不和前面的发生冲突,所以都有m-1种选择,最终的不发生冲突的种类数就是m∗(m−1)n−1,然后相减对100003取模。我最后关头把这个代码拷贝进去了,也不知道是否成功,因为python没有溢出的概念,这个结果应该可以过比较多的用例。
据说上面的可以过,但是考虑这两个幂结果可能会非常大,可以考虑用模幂运算来简化,记m′为M则有m∗(mn−1−(m−1)n−1)%p=M∗(Mn−1−(M−1)n−1),这样就更加不容易溢出了。
代码示例
m,n= [int(i) for i in input().split()]
m = m%10003
print (m*(m**(n-1)-(m-1)**(n-1))%100003)
很简单的一道题,但是当时没有投入太多时间,可惜了。之后据某些同学说在python中这样写的代码也会超时,需要进一步优化,这里就使用快速幂做出修改。下面是代码。
快速幂求解代码示例
m,n= [int(i) for i in input().split()]
m = m%10003
power = n-1
a,b = 1,1
basem, basem1 = m, m - 1
# 快速幂部分,求m和m-1的n-1次方,顺带对100003取模了
while power:
if power&1:
a = (a * basem) % 100003
b = (b * basem1) %100003
power >>= 1
basem = (basem*basem) % 100003
basem1 = (basem1*basem1) % 100003
print (m*(a-b)%100003)
题目描述
有n个物品,每个有k个属性,ai,j表示第i件物品的第j个属性,两个物品被称为完美配对需要满足两个物品的任意一个属性之后相等,即ai,j+ak,j=ai,0+ak,0,然后求完美配对的个数
输入描述
第一行n k表示物品数和属性数
接下来n行每行k个数表示第i个物品的k个属性输出一个数字表示完美配对数
输入
5 3
2 11 21
19 10 1
20 11 1
6 15 24
18 27 36输出
3
第一直观感觉就是把之前的物品存起来,然后逐个去计算每个物品是否和之前的是完美配对,判断每一个是否是完美配对需要比较至多O(k)次,这样复杂度是O(n2k)。只能过0.3的用例。
考虑到每一个数据,只需要对第一个数据做差就能根据第一属性之后的值找到它的完美配对。建立一个哈希表,只存储第二个属性开始减第一个属性的值,这样如果有一个物品从第二个属性开始,也对那个物品的第一个属性值做差,相当于ai,j+ak,j−ai,0−ak,0=0则是完美匹配。这样就可以把每一种情况使用哈希表存储,快速求出与之对应的完美匹配是否存在。如果存在,存在的数目是多少,可以与所有的情况构成完美配对,就加上这个数即可。
此时的复杂度就是对每一个数哈希,然后看是否存在哈希表中,复杂度是O(nk)
去牛客上看看别人的讨论,有人不知道自己的思路与这个很相似但是过不了,可能就是因为没有考虑,如果一个物品的所有属性都是一样的,显然自己和自己的完美配对是不能算在内的。
代码示例
from collections import defaultdict
n,k = [int(i) for i in input().split(' ')]
keys= defaultdict(lambda:0)
b = [0] * (k-1)
res =0
for i in range(n):
a = [int(i) for i in input().split(' ')]
for i in range(1,k):
a[i] -= a[0]
b[i-1] = -a[i]
if tuple(b) in keys:
res += keys[tuple(b)]
t = tuple(a[1:])
keys[t] +=1
print(res)
题目描述
有107个用户,编号从1开始,这些用户中有m个关系,每一对关系用两个数x,y表示,意味着用户x和用户y在同一圈子,关系具有传递性,A和B是同一个圈子,B和C是同一个圈子,则A,B,C就在同一个圈子。问最大的圈子有多少个用户。
输入描述
第一行输入一个整数T,表示有T组测试数据
每一组测试用例的第一行是一个数n,表示有n对关系
接下来的n行,表示这组测试数据的n对关系,每行两个数x,y
输出T行,表示每一组测试用例最大的圈子人数输入
2
4
1 2
3 4
5 6
1 6
4
1 2
3 4
5 6
7 8输出
4
2
这个题是个基于并查集的问题。我不了解并查集,所以这个题比较暴力,只过了0.4,优化之后只过了0.45,然后就花了不少时间,也没搞出来。关于并查集,大家可以自己在网上搜索讲解。
这道题我就先讲解一下不使用并查集该如何做,使用普通的集合来做,如果一个关系的两个数已经出现过了,并在出现在不同的集合中,就把两个集合取并,如果一个出现在集合中了,就把另一个数加到集合中,如果两个都没出现,则创建一个新的集合,只包含这两个数。最后对所有的集合最最大长度,这样的代码我只过了0.4,复杂度比较大的地方在于需要遍历集合列表。
然后优化一下,对每一个数简历一个哈希表,哈希表的值指向一个集合,得到一个关系之后,如果两个数都有各自的集合,则把y集合里所有的元素指向的集合换成x的集(这里的复杂度是蛮大的)。这个时候我过了0.45。
如果建立一个图,有关系则表示有边连接,然后使用遍历算法找到连通分量,对整个图遍历一遍,因为题目说了节点的数目多于关系的数目,稀疏图,使用邻接矩阵存储。这个方法是我后来想到的。
最后就是并查集了,并查集为了防止树的高度线性增长,别人通过设置树的高度来减缓高度的增加,这里我直接通过增加树的宽度来减缓树的高度的增加,那就是在递归查找父节点的时候,如果父节点不是根节点,直接把父节点改为根节点。如果有问题,欢迎大家指出。下面是代码。
代码示例
from collections import defaultdict
T = int(input())
for i in range(T):
A = []
n = int(input())
node_father = {}
def getroot(node):
if node not in node_father:
node_father[node] = node
elif node_father[node_father[node]] != node_father[node]:
# 如果父节点不是根节点,递归查找根节点,并把父节点设置为根节点
node_father[node] = getroot(node_father[node])
return node_father[node]
for j in range(n):
edge =[int(i) for i in input().split(' ')]
if edge[0] not in node_father:
node_father[edge[0]] = getroot(edge[1]) # 包含处理了两个节点都不存在的情况
elif edge[1] not in node_father:
node_father[edge[1]] = getroot(edge[0])
else:
node_father[getroot(edge[0])] = getroot(edge[1])
res = 0
nodenum = defaultdict(lambda: 0)
for node in node_father:
root = getroot(node)
nodenum[root] += 1
res = max(res, nodenum[root])
print(res)