2020年騰訊實習生算法筆試題目(感觸良多)


  參加了騰訊20年的實習生筆試,本來都不打算寫這種筆試的題目。但是感覺着產生的想法很多。首先聲明我不是什麼大佬,下面寫的內容沒有得到印證的地方還是會出現偏差,希望各位指正。這裏就先談談感想,如果筆試編程存在多個題目,而這裏有的部分題目不是一下做出來,那麼這場筆試就已經變成了多目標優化,而事實是往往很難找到最優點,不知道如何分配時間使你的收益最大化。所以事實就是,往往的結果不是你的收益最大化。
  也許你的堅持浪費在了不該有的題目上,也許再努力一步就會有結果的題目,你卻提前終止,所以結果不重要,沒必要爲了不是最優的結果而悔恨。下面看題目。

題目描述

有一個人需要去打怪物,每打一個怪物需要耗費xix_i的血量,但是會獲得yiy_i的金幣,然後開局可以使用金幣去購買血量,一個金幣可以購買qq點血量,用不完的血量所有怪物結束之後不會保留,每一個怪物都可以選擇打或者不打,問最後結束時,可以獲得最大的收益是多少。

輸入描述
第一行輸入n qn\ q,分別表示總共有n個怪物,和一個金幣可以購買q點血量
接下來的n行,分別是xi yix_i\ y_i分別是打一個怪物的消耗和金幣收益

輸入
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=2Axy^2=2Axy=Bx+Cy=Bx+C兩個曲線所圍出的面積。如果沒有所圍面積,則輸出0

輸入描述
第一行一個數n,表示測試用例的組數
接下來n行,每行輸入三個數,分別是A B CA\ B\ C
每個用例輸出一個數,表示面積,相對誤差在10410^{-4}都算對

輸入
1
1 1 -6

輸出
31.2481110540(大概是這樣,反正這樣是對的)

  每一個學過高等數學的同學都知道這個題要用定積分處理,所以肯定是先求交點,可能沒有交點,此時所圍面積就是0了。第一個函數是yy的二次函數,看起來不方便,把x和y交換,然後聯立方程求交點。y=x22A=xCBy=\frac{x^2}{2A}=\frac{x-C}{B},就能得到二次函數,最後化簡得到判別式>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 nm\ n分別表示可選數字的範圍和房間的數目。輸出衝突的種類,並對100003取模

輸入
2 3

輸出
6

  對於樣例輸入,發生衝突的情況分別是(1,1,1),(1,1,2),(1,2,2),(2,1,1),(2,2,1),(2,2,2)
  這個問題第一遍沒想出來是錯的,後面想到了可以考慮總的排列數目減去不發生衝突的數目。總的排列數目就是mnm^n,然後不發生衝突,第一個人有m個選擇,第二個人只需要不和第一個人衝突即可,有m-1種選擇,後一個人只需要保證不和前面的發生衝突,所以都有m-1種選擇,最終的不發生衝突的種類數就是m(m1)n1m*(m-1)^{n-1},然後相減對100003取模。我最後關頭把這個代碼拷貝進去了,也不知道是否成功,因爲python沒有溢出的概念,這個結果應該可以過比較多的用例。
  據說上面的可以過,但是考慮這兩個冪結果可能會非常大,可以考慮用模冪運算來簡化,記mMm'爲M%p則有m(mn1(m1)n1)%p=M(Mn1(M1)n1)m*(m^{n-1}-(m-1)^{n-1})\% p=M*(M^{n-1}-(M-1)^{n-1})%p,這樣就更加不容易溢出了。

代碼示例

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,ja_{i,j}表示第i件物品的第j個屬性,兩個物品被稱爲完美配對需要滿足兩個物品的任意一個屬性之後相等,即ai,j+ak,j=ai,0+ak,0a_{i,j} + a_{k,j} = a_{i,0} + a_{k,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(k)次,這樣複雜度是O(n2k)O(n^2k)。只能過0.3的用例。
  考慮到每一個數據,只需要對第一個數據做差就能根據第一屬性之後的值找到它的完美配對。建立一個哈希表,只存儲第二個屬性開始減第一個屬性的值,這樣如果有一個物品從第二個屬性開始,也對那個物品的第一個屬性值做差,相當於ai,j+ak,jai,0+ak,0=0a_{i,j} + a_{k,j} - a_{i,0} + a_{k,0}=0則是完美匹配。這樣就可以把每一種情況使用哈希表存儲,快速求出與之對應的完美匹配是否存在。如果存在,存在的數目是多少,可以與所有的情況構成完美配對,就加上這個數即可。
  此時的複雜度就是對每一個數哈希,然後看是否存在哈希表中,複雜度是O(nk)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)

題目描述

10710^7個用戶,編號從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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章