给定一个数组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。
一到考试有点慌,知道要用动态规划,然后就想怎么用。这个问题可以看成是在矩阵中寻找一个路径,要求整个路径的前向之差绝对值最小。可以想如何把问题规模缩小,显然下一个数的选择,可以有三条路径,如果从第一行过来,那么就需要用到选了第一行的路径的和的最小值,同样也需要求出用到第二行和第三行过来的路径的最小值。
可见这是一个动态规划问题。我们定义一个动态规划数组, d p i j dp_{ij} dpij表示选择了矩阵中(i,j)位置的元素,最小的路径绝对值之和。显然最终的结果就是最后一列三个路径最小值。
d p i j = m i n ( a b s ( A [ i ] [ j ] − A [ i − 1 ] [ k ] ) + d p i − 1 , k ) k ∈ 0 , 1 , 2 dp_{ij}=min(abs(A[i][j]-A[i-1][k])+dp_{i-1,k}) \quad k\in{0,1,2} dpij=min(abs(A[i][j]−A[i−1][k])+dpi−1,k)k∈0,1,2
好了,有公式可以写代码了。我直接给出我的AC代码,尴尬的就是直接想把时间复杂度和空间复杂度写到最小。(完美主义害死人,在这里多花了几分钟的时间。)
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表示缺失数据。 − 1 0 9 ≤ A i j ≤ 1 0 9 -10^9≤A_{ij}≤10^9 −109≤Aij≤109
接下来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。
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 |
代入线性方程组可以解出来整个矩阵,但是这可能是线性代数的内容了,如果编程题这样出,我觉得不太可能。不过用上述两个代码跑这个实例,根本解不出这个矩阵,因为四个点都不在同一行或者同一列,上面的代码无法求出任何一个公差,所以认为这是不可解的(实际可解)。这里给出一个证明结论,如果四个点中有三个点来自于一行(或一列),则无法解出这个方程组。如果四个点,两个点是一行,另外两个点在一列上,也是无法解出这个矩阵。