今天做到力扣的求不相交的线的数量
本质上是求两个数组的最长公共子序列的长度,之前学的算法基础已经忘了,现在来回顾一下。
首先弄明白什么是子序列,子序列是在一个数组中,按照索引递增的顺序取部分元素组成的序列。元素不一定挨着,但是要与原数组的顺序保持一致,也就是前面说的索引递增的顺序。
用数学公式表达:数组A={a1,a2,...,am},那么数组 A的子序列 A'={ai,aj,..ak} i<j<...<k i,j,k∈[1,m]
最长共子序列不唯一,但是最长公共子序列的长度是唯一的。
如果我们要求数组A和数组B的最长公共子序列,数组B表示为B={b1,b2,...,bn},假设数组Z是最终的最长公共子序列Z={z1,...,zl},长度为l
- 如果am==bn,那么zl一定等于am/bn。(这个很好明白,A,B数组最后一个元素相等的话,最长公共子序列中必包含该元素,包含该元素不会影响到前面的元素,只是追加而已)
- 如果am!=bn且zl!=am,那么A数组去掉最后一个元素A{m-1}和B数组的最长公共子序列还是Z数组(也就是说am这个元素不影响求A,B的最长公共子序列)
- 同理,如果am!=bn且zl!=bn,那么B数组去掉最后一个元素B{n-1}和A数组的最长公共子序列还是Z数组(也就是说bn这个元素不影响求A,B的最长公共子序列)
将上面的思路整理一下,我们要讨论的就是A数组和B数组的末尾元素是否相等,如果相等最长公共子序列必包含该元素;如果不相等,A,B的最长公共子序列长度就是
max(数组A{m-1}与数组B的最长公共子序列长度 , 数组A和数组B{n-1} 的最长公共子序列的长度)
因为我们不知道am等于zl还是bn等于zl,所以肯定是两个都算出来,再取最大值。
上面的讨论是基于数组的末尾元素进行讨论的,最后想求最长公共子序列的长度,我们不能把AB数组整体来看,得先让A数组有一个元素然后讨论,再追加一个元素然后讨论,同理B数组也是,这样才能求出。所以我们使用一个二维数组c[][],其中第i行表示将A数组截断到第i个元素,第j列 表示将B数组截断到第j的元素,ai,bj 作为末尾元素进行讨论,c[i][j]表示截断到第i个元素的A数组和截断到第j个元素的B数组的最长公共子序列的长度,那么有:
可以发现,当我们判断A数组第一个元素(下标是0)和B数组第一个元素的时候c[i-1]c[j-1]都会出问题,所以c二维数组在创建的时候多创建一行和一列,这样对于c[i-1]和c[j-1]就不会有问题了,多出来的一行和一列默认值是0(java和C应该默认值是0其他的不知道),不会影响我们的比较。上面的公式就会变成:
我们从c[1][1]开始遍历,一行一行遍历,最后c[A的数组长度][B的数组长度]就是答案。
代码:
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int [][] c=new int[nums1.length+1][nums2.length+1];
for(int i=1;i<=nums1.length;i++)
{
for(int j=1;j<=nums2.length;j++)
{
if(nums1[i-1]==nums2[j-1])
{
c[i][j]=c[i-1][j-1]+1;
}else{
c[i][j]=Math.max(c[i-1][j],c[i][j-1]);
}
}
}
return c[nums1.length][nums2.length];
}
可以自己拿个例子试一下,填格子很快的,比较当前两个末尾元素是否相等,相等就拿对角线元素加一;不相等就看上面一个元素和左面一个元素谁更大。填的很快~
参考文章: