[每日一题]Leetcode300. 最长递增子序列和Leetcode354. 俄罗斯套娃信封问题

本文详细介绍了LeetCode 300题最长递增子序列的两种解法,并通过对比解析了LeetCode 354题俄罗斯套娃信封问题的解决思路,包括排序策略与动态规划的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021/3/4
今天正式开始每日一题,今天的题是Leetcode354. 俄罗斯套娃信封问题,在做此题之前先要了解Leetcode300. 最长递增子序列,所以我们先以此题入手。
300题题意为

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

第一个想到的是动态规划,定义dp[i]为以nums[i]为结尾的最长递增子序列,所以转移方程为从0到i遍历每一个j,如果j位置的数大于i位置的数则可以将j位置的数放在i位置并后面长度加一。所以状态转移方程为当nums[j]>nums[i]时,dp[i] = max(dp[i], dp[j] + 1);
这样我们就可以找到最大的dp的值来找到最长的子序列。
c++参考代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
     int n=nums.size();
     if(n==0)
     return 0;
     vector<int> dp(n,0);
     int ans=0;
      for(int i=0;i<n;i++)
      {
          dp[i]=1;
          for(int j=0;j<i;j++)
          {
              if(nums[j]<nums[i])
             dp[i]=max(dp[i],dp[j]+1);
          }
          ans=max(ans,dp[i]);
      }
     return ans;     
   }
};

时间复杂度:O(N^2)。空间复杂度:O(N)。

我们发现,每一次更新i都要遍历从0到i,太过浪费时间,考虑优化,这里我们可以使用贪心算法。以贪心思路考虑,当前子序列结尾元素越小,后续可能接在后面的元素就越多,所以我们每更新一个nums[i],要么把它放到最后,要么用它替换前面大的数(为之后的更新做准备),反正我们的最终目的只是子序列的长度最长,在长度不变的情况下子序列的每一个值肯定越小越好。所以我们维护一个数组tail,这样每次更新一个nums[i]会出现以下两种情况:
1.nums[i]>tail的最后一个数,那么将nums[i]放到tail后面,子序列长度加一。
2.nums[i]<tail的最后一个数,那么找到tail里第一个大于(或等于)nums[i]的数,将它变成nums[i],子序列长度不变。(等于时情况一样,替换与否都一样。)

这样我们维护的tail数组就是最长的子序列,tail的长度即为答案。
c++参考代码:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
     int n=nums.size();
     if(n==0)
     return 0;
       vector<int> tail;
       tail.push_back(nums[0]);
       int end=0;
       for(int i=1;i<n;i++)
       {
         if(nums[i]>tail[end])
           {
             tail.push_back(nums[i]);
             end++;
             }
          else//这里使用二分查找缩短时间复杂度。
          {
              int l=0,r=end;
              while(l<r)
              {
                 int mid=(l+r)>>1;
                  if(tail[mid]<nums[i])
                  l=mid+1;
                  else
                  r=mid;
              }
              tail[l]=nums[i];
          }
       }
       return end+1;
       }
};

时间复杂度:O(N log N),空间复杂度:O(N)。

这一题做完后,我们现在有实力来看Leetcode354. 俄罗斯套娃信封问题了,题意为:

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。(不允许旋转信封。)

首先这一题与之前一题的第一个区别就是消除了子序列的限制,这样我们就可以愉快的进行排序了。其实这一题也就是在上一题的基础上变成了二维的情况,可以考虑将两个维度分开考虑。将一个维度排序后,另一维度就变成了上面那道题,但是要考虑一种特殊情况,如过我们单纯的按照递增排序,排完序的结果为 [(w, h)] = [(1, 1), (1, 2), (1, 3), (1, 4)],这样我们忽略w维度,剩下元素为[1,2,3,4],这样长度为4,但实际上因为w相同长度只能为1。所以为了考虑w维度相同的情况,我们将w维度还是递增排序,w相同时h递减排序,这样相同的w只有一个h会被计算。
同样对应上一题有两种解法。
动态规划c++代码:

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        if (envelopes.empty()) {
            return 0;
        }
        int n = envelopes.size();
        sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });
       vector<int> dp(n,1);
        int ans=1;
      for(int i=1;i<n;i++)
      {
          for(int j=0;j<i;j++)
          {
             if (envelopes[j][1] < envelopes[i][1]) 
             {
              dp[i]=max(dp[i],dp[j]+1);
             }
          ans=max(ans,dp[i]);
      }  
    }
         return ans;   
    }
};

时间复杂度:O(N^2)。空间复杂度:O(N)。

状态转移c++代码:

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
    if(envelopes.empty())
    {
        return 0;
    }
      sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });
          vector<int> tail= {envelopes[0][1]};
        for(int i=1;i<envelopes.size();i++)
        {
            int now=envelopes[i][1];
            if(now>tail.back())
            {
                tail.push_back(now);
            }
            else{
                 auto it = lower_bound(tail.begin(), tail.end(), now);
                *it = now;
            }
        }
        return tail.size();
    }
};

时间复杂度:O(N log N),空间复杂度:O(N)。

参考资料
·力扣官方题解
·liweiwei1419《动态规划(包含O (N log N) 解法的状态定义以及解释)》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值