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,结束后做一做这个题目的分析。根据题意,如果一个矩阵中可以确定两行或者两列就可以完全确定这个矩阵。如何确定两行或者两列呢,这两行和这两列必须有两个以上的数字。如果有两个以上的数字,则可以对这行或列求出公差,整行或列就可以确定。
所以我觉得求出公差是比较关键的一步,我的代码直接奔着求出公差去了。一旦求出公差,则只需要保存该行或者列的一个数就可以确定整行整列。下面看我求出公差的代码。
求公差的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)
else:
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[j][p])//(j-p)
else:
break
for i,j in Q:
i=i-1
j=j-1
if A[i][j]:
print(A[i][j])
elif row[i]==-1 and col[j]==-1:
print('Unknown')
elif numRow[i]!=-1:
print(A[i][numRow[i]]+((j-numRow[i])*row[i]))
else:
print(A[i][numRow[i]]+(numRow[i]*row[i]))
如果不能计算出整个矩阵的话,我的代码到这也就结束了。但是我提交的时候,时间结束了,bug还没有修复,这个代码也没有得到验证。
而且这个题目最难的地方在于,最少有四个点就可以求出整个矩阵,因为这个行和列的等差数列矩阵秩是小于2的。而且可以证明,行和列的公差也是个等差数列,而这个时候我们称之为二阶公差,行和列的二阶公差是相等的。感谢我的师兄的讨论,和给我的启发。
举个四个数可以确定整个矩阵,但是无法求出任何一个行或列的公差的情况。
4 | 0 | 0 | 0 |
0 | 0 | 0 | 18 |
0 | 0 | 24 | 0 |
0 | 26 | 0 | 0 |
代入线性方程组可以解出来整个矩阵,但是这可能是线性代数的内容了,如果编程题这样出,我觉得不太可能。
如果可以根据两行的公差和两列的公差把整个矩阵确定的代码我没写,这个代码有点难,我觉得没继续写下去的必要了,因为写起来有点复杂。或许是我想错了,希望大家能够在这里提供思路。或者我后面补充。