动态规划(dp)
题目详情
给出 n
个数对。 在每一个数对中,第一个数字总是比第二个数字小。
现在,我们定义一种跟随关系,当且仅当 b < c
时,数对(c, d)
才可以跟在 (a, b)
后面。我们用这种形式来构造一个数对链。
给定一个数对集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
示例1:
输入:[[1,2], [2,3], [3,4]]
输出:2
解释:最长的数对链是 [1,2] -> [3,4]
思路:
这道题和leetcode-300.最长子序列是一样的,首先明确dp是指什么,dp[i]
表示以( pairs[i][0], pairs[i][1] )
这个数对结尾的最长链长度,和300题一样,我们也需要定义一个max_pairs
来同步更新我们需要的最最长链
详细思路可以参看300题的解答:
leetcode-300.最长递增子序列
注意这道题可以改变数对的顺序,所以要先sort一下数对再进行判断
我的代码:
class Solution
{
public:
int findLongestChain(vector<vector<int>>& pairs)
{
if (pairs.empty()) return 0;
//将各个数对按从小到大排列一下
sort(pairs.begin(), pairs.end(), [](const vector<int>& a,const vector<int>& b)
{
return (a[0] == b[0] && a[1] < b[1]) || (a[0] < b[0]);
});
int n = pairs.size(), max_pairs = 0;
vector<int> dp(n, 1); //以每个数对结尾的最长数对链,初始化为1
//外层循环
//遍历每个数对[ pairs[i][0], pairs[i][1] ]
for (int i = 0; i < n; ++i)
{
//内层循环
//遍历这个数对前面的所有数对
for (int j = 0; j < i; ++j)
{
//要保证i和j这两个数对没有交集才能组成链
if (pairs[j][1] < pairs[i][0])
dp[i] = max(dp[i], dp[j] + 1); //比较更新i结尾的最长链长度
}
max_pairs = max(max_pairs, dp[i]); //同步更新最最长链
}
return max_pairs;
}
};
同样的,我们可以像300题一样利用二分查找加速优化:
class Solution
{
public:
//解法2:动态规划+二分法,时间复杂度O(nlogn),空间复杂度O(n)
int findLongestChain(vector<vector<int>>& pairs){
if(pairs.empty())return 0;
sort(pairs.begin(), pairs.end(), [](const auto& a,const auto& b)
{
return (a[0] < b[0]) || (a[0] == b[0] && a[1] < b[1]);
});
//上面部分不变
//下面我们用dp二维数组反复更新最后得到一个最长的数对链
vector<vector<int>> dp;
//遍历每个pairs数对
for (auto& pair : pairs)
{
//二分法在dp数组中寻找大于等于pair[0]的最小值dp[i][1] (目的是为了把pair插入dp中)
int left = 0, right = dp.size();
while (left < right)
{//进入while循环区间内至少有2个元素,退出循环的极值只有0或size
int mid = left + ((right - left) >> 1); //这里相当于mid = (left+right) / 2
if(pair[0] <= dp[mid][1]) //pair[0]需要找到尾小于他首的地方--向左缩小区间寻找
right = mid; //则需要把搜索的dp区间缩小为[right,mid]
else
left = mid + 1; //否则就是在[mid + 1, right]区间中继续搜索
}
//最后[left, right]定位到一个合适的地方(dp最小右端点能插入pair的)
//left >= dp.size()说明二分没有搜索到大于等于pair[0]的dp[i][1]
//说明dp[size-1][1] < pair[0] (pair可以插入dp尾部)
if (left >= dp.size()) dp.emplace_back(pair);
//这个pair的左端小于(等于)dp[left][1] 右端小于dp[left][1],那么就用pair取代left处的区间,
//这样可以将left变小,以便形成最长的数对链
else if (dp[left][1] > pair[1]) dp[left] = pair;
}
return dp.size();
}
};
还有一种贪心的解法,可以将本题转化为类似leetcode-435.无重叠区间的题435题是求移除的区间数,本题只要稍作转化,求移除后剩下的区间数
leetcode-435.无重叠区间
class Solution
{
public:
int findLongestChain(vector<vector<int>>& pairs)
{
if(pairs.empty()) return 0;
//注意这里要变成以尾元素优先的顺序sort
sort(pairs.begin(), pairs.end(), [](const auto& a,const auto& b)
{
return (a[1] < b[1]) || (a[1] == b[1] && a[0] < b[0]);
});
//count初始化为1,用来统计不重复子区间个数的
int count = 1, end = pairs[0][1];
for(const auto& p : pairs)
{
if(p[0] > end)
{//区间不相交,需要更新边界以及不重复区间个数,注意不能有等号,即区间端点不能连续
count++;
end=p[1];
}
}
return count;
}
};
涉及知识点:
1.动态规划(dp)