LeetCode300. 最长递增子序列(动态规划 / 贪心)

力扣

 

                

解题思路:

1.动态规划

  • 状态定义:dp[i] 的值代表 nums 以 nums[i] 结尾的最长子序列长度。
  • 转移方程: 设 j∈[0,i)j∈[0,i),考虑每轮计算新 dp[i] 时,遍历[0,i) 列表区间,做以下判断:
  1. 当 nums[i]>nums[j] 时: nums[i] 可以接在 nums[j] 之后(此题要求严格递增),此情况下最长上升子序列长度为 dp[j]+1 ;当 nums[i]<=nums[j] 时:nums[i] 无法接在nums[j] 之后,此情况上升子序列不成立,跳过。
  2. 上述所有 1. 情况 下计算出的 dp[j]+1 的最大值,为直到 i 的最长上升子序列长度(即 dp[i] )。实现方式为遍历 j 时,每轮执行 dp[i]=max(dp[i],dp[j]+1)。
  • 转移方程: dp[i] = max(dp[i], dp[j] + 1) for j in [0, i)。
  • 初始状态:dp[i] 所有元素置 1,含义是每个元素都至少可以单独成为子序列,此时长度都为 1。
  • 返回值:返回dp 列表最大值,即可得到全局最长上升子序列长度。
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
       int size = nums.size();
		if (size == 1)
			return 1;

		vector<int> dp(size, 1); //初始化
		int ret = 0;

		for (int i = 1; i < size; ++i)
		{
			for (int j = 0; j < i; ++j)  //状态转义方程
			{
				if (nums[j] < nums[i])
				{
					dp[i] = max(dp[i], dp[j] + 1);
				}
			}

		}

		for (auto& e : dp)
		{
			if (ret < e)
			{
				ret = e;
			}
		}

		return ret;
		
    }
};

                        

                        

2.贪心 + 二分

算法流程是:

1 . 维护一个数组d,该数组d是一个递增的数组,满足后续二分法查找降低算法复杂度;
2 . 每次从nums数组中按顺序拿出一个元素nums[i];
3 . 若nums[i]比d数组中的最后一个元素大,则将nums[i] append到数组d中(显然能保证d递增);
4 . 若nums[i]比d数组中的最后一个元素小,则在d中找到比nums[i]小但最接近nums[i]的数d[k],(即若从后往前遍历数组b的话,找到的是b中第一个比nums[i]小的元素b[k]),并将d[k]后面一个数替换掉(显然能保持d递增);
(这里不用什么高大上的反证法,直接理解就能知道d数组必然保持递增)
那么为什么这么做就是正确答案呢?
首先,我们要找的是最长递增子序列,该方法的思想就是在给定长度的数组中要尽可能的找长的递增子序列,则要求子序列中的元素增长得越慢越好。
然后要抓住的要点是:
!!!数组d维持的是与最长子序列等长的子序列,d中元素的顺序不一定和nums中的子序列顺序一致,但是答案是正确的,因为只需要求子序列长度!!!

为什么数组d能做到这个?
首先数组d中插入的是nums数组的第一个元素,这没什么可说的,长度就是1;
然后拿nums数组中的下一个元素去和d中的最后一个元素比较大小
如果进入步骤3,即nums中的这个元素比咱们维护的递增子序列中的最大数大,则最长子序列长度增加1,d中append上这个元素,该序列就是此时递增的最长子序列了。如果进入步骤4,说明这个元素不能使我们维护的最长子序列长度增加,我们要保证此时下面的操作不会改变我们维护的最长递增子序列的长度,也就是d的长度。 可是根据该方法思想的精髓,元素增加速度要慢!在不改变最长子序列长度的前提下,我们要使维护的递增子序列增长速度变慢,期待它后续能更长。
怎么变慢?将之前维护的数组d中的较大元素与我们现在拿到的小的nums[i]替换!
为什么是替换?因为替换不改变数组长度,可以维持答案正确!
为什么是替换掉第一个比nums[i]大的元素?因为我们要维护d数组是递增的!
举个例子:nums=[0,8,4,12,24,3,5,12]
d=[0]
d=[0,8]
d=[0,4]
d=[0,4,12]
d=[0,4,12,24]
d=[0,3,12,24]
d=[0,3,5,24]
d=[0,3,5,12]
我们可以看到,其实这个最长递增子序列可以是0,4,12,24;也可以是我们得到的0,3,5,12,但是,后者比前者增长更加缓慢,如果nums后续还有元素,那么我们可以期待0,3,5,12这个递增子序列更有利于扩展成更长的递增子序列,因为比12大的元素区间是[12,正无穷],比24大的元素区间则为[24,正无穷],前者范围更大,元素落入该区间的可能性更大,这就是该方法的思想精髓!

作者:jia-luo-i
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/guan-fang-fang-fa-er-de-tong-su-li-jie-x-r1yx/
来源:力扣(LeetCode)

这张图也能帮助我们理解这个思想:B站up讲解视频链接奉上

 

       

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
      vector<int> ret;
      ret.push_back(nums[0]);

      for(int i = 1 ; i < nums.size() ; ++i)
      {
         auto it = lower_bound(ret.begin() , ret.end() , nums[i]);
         if(it == ret.end())
         {
             ret.push_back(nums[i]);
         }
         else
         {
             *it = nums[i];
         }
      }


      return ret.size();
    }
};

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值