1035. 不相交的线
思路:
虽然能看出来是动态规划了,但是还是写不出。我想要如何判断交叉呢,官方题解用的动规 就很巧妙,没有直接涉及判断交叉,但却解决了这个问题。
dp[i][j]定义为nums1[0:i]连接nums2[0:j]的最大连线数
我们是要两重for循环的 ,对于dp[i][j],我们判断nums1[i]nums2[j],如果相等说明可以连接,然后dp[i][j]=dp[i-1][j-1]+1,这里用dp[i-1][j-1]是因为对于i-1这个位置,它连线就不会超过j-1,因此对于i连线j的时候就不会产生交叉的线。
如果nums1[i]!=nums2[j]呢,我们需要取dp[i-1][j]和dp[i][j-1]中的最大值,这样取保证获取了上一步最大的连线数。nums1[i]nums2[j]的时候不用dp[i-1][j]或者dp[i][j-1]是因为会导致连接同一个数例如nums1[i-1]连接了nums2[j]但nums1[i]连接了nums2[j].
代码:
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int n1=nums1.size();
int n2=nums2.size();
vector<vector<int> > dp(n1+1,vector<int>(n2+1)); //这里顺便把dp[0][j]初始化为0.
for(int i=1;i<=n1;++i){
for(int j=1;j<=n2;++j){ //从1开始是因为会减少边界条件的判断会更方便,也更好理解 dp[1][1]就理解为nums1的第一个数和nums2的第二个数的最大连线数。
if(nums1[i-1]==nums2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[n1][n2];
}
};
然后我们可以发现,我们只需要三个数,即dp[i-1][j-1],dp[i-1][j],dp[i][j-1],所以我们可以优化消耗的空间,化成一维。
我们仍然需要两重for循环,我们定义dp[j]为0:i连接到当前j的最大连线数。
我们的状态转移方程为:
如果nums1[i]=nums2[j],dp[j]=dp[j-1]+1
如果nums1[i]!=nums2[j],dp[j]=max(dp[j-1],dp[j])
所以dp[j]会随着i的增加而改变,那么问题就来了,那么我们需要的dp[j-1]就会改变了,所以在我们更新dp[j]之前要先记录它的值.(对于当前dp[j]来说,他需要的dp[j-1]是原来二维的dp[i-1][j-1],到了dp[j]需要它的时候,它到当前的j之前就被dp[i][j-1]覆盖了,因此我们要记录它改变之前的值)相当于每一个i,都有一次dp[j]的改变。
因为每个i开始每个j都会用它前面一个dp[j-1]所以每个i的循环开始的时候都要让last重新等于dp[0]
代码:
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int n1=nums1.size();
int n2=nums2.size();
vector<int> dp(n2+1);
for(int i=1;i<=n1;++i){
int last = dp[0];
for(int j=1;j<=n2;++j){
int temp = dp[j];
if(nums1[i-1]==nums2[j-1]){
dp[j]=last+1;
}
else dp[j]=max(dp[j],dp[j-1]);
last = temp;
}
}
return dp[n2];
}
};