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。
一到考試有點慌,知道要用動態規劃,然後就想怎麼用。這個問題可以看成是在矩陣中尋找一個路徑,要求整個路徑的前向之差絕對值最小。可以想如何把問題規模縮小,顯然下一個數的選擇,可以有三條路徑,如果從第一行過來,那麼就需要用到選了第一行的路徑的和的最小值,同樣也需要求出用到第二行和第三行過來的路徑的最小值。
可見這是一個動態規劃問題。我們定義一個動態規劃數組,表示選擇了矩陣中(i,j)位置的元素,最小的路徑絕對值之和。顯然最終的結果就是最後一列三個路徑最小值。
好了,有公式可以寫代碼了。我直接給出我的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表示缺失數據。
接下來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 |
代入線性方程組可以解出來整個矩陣,但是這可能是線性代數的內容了,如果編程題這樣出,我覺得不太可能。不過用上述兩個代碼跑這個實例,根本解不出這個矩陣,因爲四個點都不在同一行或者同一列,上面的代碼無法求出任何一個公差,所以認爲這是不可解的(實際可解)。這裏給出一個證明結論,如果四個點中有三個點來自於一行(或一列),則無法解出這個方程組。如果四個點,兩個點是一行,另外兩個點在一列上,也是無法解出這個矩陣。