leetcode-646. 最长数对链

动态规划(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)

动态规划

posted @ 2022-07-13 12:13  ggaoda  阅读(3)  评论(0编辑  收藏  举报  来源