2020年3月25日阿里筆試題


  彷彿人生總有一種魔咒,自己做的這場筆試題永遠是最難的。不過今天的筆試題,真的難。來看題目。

題目描述一

給定一個數組n,然後給三個長度爲n的數組,可以從這三個數組中選出一個長度爲n的數組,第i個位置需要是從給出的三個數組第i個位置選擇的,然後要求使這個數組後一項減前一項的絕對值之和最小。
輸入示例::
5 9  5 4  4
4 7  4 10 3
2 10 9 2  3
這裏可以選擇5 7 5 4 4,所以輸出等於|7-5|+|5-7|+|4-5|+|4-4|=5。所以輸出就是5

  一到考試有點慌,知道要用動態規劃,然後就想怎麼用。這個問題可以看成是在矩陣中尋找一個路徑,要求整個路徑的前向之差絕對值最小。可以想如何把問題規模縮小,顯然下一個數的選擇,可以有三條路徑,如果從第一行過來,那麼就需要用到選了第一行的路徑的和的最小值,同樣也需要求出用到第二行和第三行過來的路徑的最小值。
  可見這是一個動態規劃問題。我們定義一個動態規劃數組,dpijdp_{ij}表示選擇了矩陣中(i,j)位置的元素,最小的路徑絕對值之和。顯然最終的結果就是最後一列三個路徑最小值。
  dpij=min(abs(A[i][j]A[i1][k])+dpi1,k)k0,1,2dp_{ij}=min(abs(A[i][j]-A[i-1][k])+dp_{i-1,k}) \quad k\in{0,1,2}
  好了,有公式可以寫代碼了。我直接給出我的AC代碼,尷尬的就是直接想把時間複雜度和空間複雜度寫到最小。(完美主義害死人,在這裏多花了幾分鐘的時間。)

python代碼

n=int(input())
a1=[int(i) for i in input().split(' ')]
a2=[int(i) for i in input().split(' ')]
a3=[int(i) for i in input().split(' ')]
A=list(zip(a1,a2,a3))
pre=[a1[0],a2[0],a3[0]]
minsum=[0,0,0]
preSum=[0,0,0]

for i in range(1,n):
    for row in range(3):
    	# 這行代碼表示對上面的公式取最小值。
        minsum[row]=min([preSum[num]+abs(A[i][row]-A[i-1][num]) for num in range(3)])
    # 爲了節省空間,我沒有開闢一個和輸入數組一樣大的空間。
    preSum=minsum.copy()
print(min(minsum))

  雖然有一次AC的成就感,但是做完這個題,已經快四十分鐘過去了,第二題我還沒看。誰知道第二題,依舊非常難。

題目描述二

給出一個二維矩陣,這個矩陣的每一行和每一列都是一個獨立的等差數列,其中一些數據缺失了,現在需要推理隱藏但是可以被唯一確定的數字,然後對輸入的查詢進行回答。

輸入描述:
第一行,n,m,q分別表示矩陣的行數,列數和查詢的條數。
接下來的n行,每行m個數表示這個矩陣,0表示缺失數據。109Aij109-10^9≤A_{ij}≤10^9
接下來q行,每行兩個數字i,j表示對矩陣中第i行第j列的數字進行查詢。

輸出描述:
如果可以確定該位置的數字,則輸出數字,如果不能確定則輸出UNKNOWN。

輸入示例:
2 3 6
1 0 3
0 0 0
1 1
1 2
1 3
2 1
2 2
2 3

輸出示例:
1
2
3
Unknown
Unknown
Unknown

  這個題目有點變態,我沒有想出什麼好辦法,提交的代碼也有bug,結束後做一做這個題目的分析。根據題意,如果一個矩陣中可以確定兩行或者兩列就可以完全確定這個矩陣。如何確定兩行或者兩列呢,這兩行和這兩列必須有兩個以上的數字。如果有兩個以上的數字,則可以對這行或列求出公差,整行或列就可以確定。
  所以我覺得求出公差是比較關鍵的一步,我的代碼直接奔着求出公差去了。一旦求出公差,則只需要保存該行或者列的一個數就可以確定整行整列。下面看我求出公差的代碼。
  這裏補充解釋一下爲什麼我要求公差,因爲求出來公差確定這行肯定是已知的,所以即便本來有元素就是0,那麼也可以正確返回。但是如果不求出公差的話,檢索到這個位置是0,無法判斷是否是Unknown還是本來就是0。

求公差的python代碼

n, m, q = [int(i) for i in input().split(' ')]
A = []
Q=[]
for i in range(n):
    A.append([int(i) for i in input().split(' ')])
for i in range(q):
    Q.append([int(i) for i in input().split(' ')])
row=[0]*n  # 求行的公差
col=[0]*m  # 求列的公差
numRow=[-1]*n # 求該行的一個數的索引
numCol=[-1]*m # 求該列的一個數的索引
for i in range(n):
    for j in range(m):
        if A[i][j]:
            p=j
            numRow[i]=j
            for j in range(j+1,m):
                if A[i][j]:
                    row[i]=(A[i][j]-A[i][p])//(j-p)
                    for j in range(m):
                        if not A[i][j]:
                            A[i][j]=A[i][numRow[i]] + ((j - numRow[i]) * row[i])
                    break
            break
for i in range(m):
    for j in range(n):
        if A[j][i]:
            p = j
            numCol[i]=j
            for j in range(j + 1, n):
                if A[j][i]:
                    col[i]=(A[j][i]-A[p][i])//(j-p)
                    for j in range(n):
                        if not A[j][i]:
                            A[j][i]=A[numCol[i]][i] + ((j - numCol[i]) * col[i])
                    break
            break

# print(A)
for i,j in Q:
    i=i-1
    j=j-1
    if row[i] or col[j] or A[i][j]:
    	print(A[i][j])
    else:
    	print('Unknown')

  如果不能計算出整個矩陣的話,我的代碼到這也就結束了。但是我提交的時候,時間結束了,bug還沒有修復,這個代碼也沒有得到驗證。
  後來在網上找到別人的實現,證明這樣做是對的。但是這樣是對的只能說明一個問題,那就是阿里的測試用例有問題,舉個四個數可以確定整個矩陣,但是上面的代碼無法確定整個矩陣的的情況。

4 5 0 0
9 0 0 0
0 0 24 0
0 0 0 0

  讓上面的代碼跑一遍,還是會有很多的空洞,我們看這些空洞,很容易想到把代碼再跑一遍就可以把整個矩陣填充完整。下面是跑兩遍,可以解決這種情況的代碼。此處更正我的一個錯誤,之前說過不需要在記錄公差,事實上還會遇到不可解的情況和此處爲0的情況同時出現出現。所以這裏還是需要記錄公差是否可求,而且求出來的公差可能是0的情況,所以記錄公差用公差的值做判斷也有瑕疵的,我下面更正成了記錄公差是否可求。

處理上述情況的代碼

n, m, q = [int(i) for i in input().split(' ')]
A = []
Q = []
for i in range(n):
    A.append([int(i) for i in input().split(' ')])
for i in range(q):
    Q.append([int(i) for i in input().split(' ')])
row=[False]*n  # 記錄該行是否公差可求
col=[False]*m  # 記錄該列是否公差可求
for time in range(2):
    for i in range(n):
        for j in range(m):
            if A[i][j]:
                p = j
                for j in range(j + 1, m):
                    if A[i][j]:
                        d = (A[i][j] - A[i][p]) // (j - p)
                        row[i]=True
                        for j in range(m):
                            if not A[i][j]:
                                A[i][j] = A[i][p] + ((j - p) * d)
                        break
                break
    for i in range(m):
        for j in range(n):
            if A[j][i]:
                p = j
                for j in range(j + 1, n):
                    if A[j][i]:
                        d = (A[j][i] - A[p][i]) // (j - p)
                        col[i]=True
                        for j in range(n):
                            if not A[j][i]:
                                A[j][i] = A[p][i] + ((j - p) * d)
                        break
                break
for a in A:
    print(a)

for i, j in Q:
    if row[i-1] or col[i-1] or A[i-1][j-1]:# 如果行或列的公差可求,該數一定可求。
        print(A[i-1][j-1])
    else:
        print('Unknown')

  但是上面的代碼還是有瑕疵的,最少有四個點就可以求出整個矩陣,因爲這個行和列的等差數列矩陣秩是小於2的。而且可以證明,行和列的公差也是個等差數列,而這個時候我們稱之爲二階公差,行和列的二階公差是相等的。感謝我的師兄的討論,和給我的啓發。
  舉個四個數可以確定整個矩陣,但是無法求出任何一個行或列的公差的情況。這個題可以利用行的二階公差和列的公差相等。把四個點帶進去,列一個線性方程組求解,具體細節不再這裏展開。

4 0 0 0
0 0 0 18
0 0 24 0
0 26 0 0

  代入線性方程組可以解出來整個矩陣,但是這可能是線性代數的內容了,如果編程題這樣出,我覺得不太可能。不過用上述兩個代碼跑這個實例,根本解不出這個矩陣,因爲四個點都不在同一行或者同一列,上面的代碼無法求出任何一個公差,所以認爲這是不可解的(實際可解)。這裏給出一個證明結論,如果四個點中有三個點來自於一行(或一列),則無法解出這個方程組。如果四個點,兩個點是一行,另外兩個點在一列上,也是無法解出這個矩陣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章