力扣-300-最长递增子序列
直达链接
想到了连续子数组的最大和
自己想
我本来想倒着推,有点像mari和shiny,但是不对
class Solution { public: int lengthOfLIS(vector<int>& nums) { int length = nums.size(); if (length < 2) return 1; vector<int> dp(length); dp[length - 1] = 1; nums[length - 2] < nums[length - 1] ? dp[length - 2] = 2 : dp[length - 2] = 1; for (int i = length-2; i > 0; --i) { if (nums[i - 1] < nums[i]) { dp[i - 1] = dp[i] + 1; } else { dp[i - 1] = dp[i]; } } return dp[0]; } };
因为没法很好地解决这两个用例:
// vector<int> nums = { 0,1,0,3,2,3 }; vector<int> nums = { 4,10,4,3,8,9 };
官方题解
1. 动态规划
数组dp[i]
表示nums
中直到i
为止(包括nums[i]
)的子数组中,最长子序列的长度
状态转移方程为:dp[i] = max(dp[j])+1,j<i且nums[j]<nums[i]
class Solution { public: int lengthOfLIS(vector<int>& nums) { int n = nums.size(); // 由题,n>1,所以省略检查 vector<int> dp(n, 0); // 遍历dp数组,每一次都将数组元素初始化为1 for (int i = 0; i < n; ++i) { dp[i] = 1; for (int j = 0; j < i; ++j) { // 遍历比i小的dp数组元素,依次比较取大值 // 以0为例:0不会进入循环 // 以1为例:dp[1]=max(dp[1],dp[0]) // 以2为例:dp[2]=max(dp[2],dp[0]),dp[2]=max(dp[2],dp[1]) if (nums[j] < nums[i]) { // 这里的dp[i]并不总等于1,在前面的二层循环中可能被刷新 // 因为j<i,所以dp[j]的值一定是知道的 dp[i] = max(dp[i], dp[j] + 1); } } } // 没见过的新函数,用于求STL容器中的最大值 return *max_element(dp.begin(), dp.end()); } };
2. 贪心+二分查找
(反正我是没想到二分查找在这里怎么用的)
贪心思想是:如果要想让子序列尽量长,就需要让序列元素的“上升”(数值增大)尽量平缓
关键在于一个数组d[i]
,表示长度为i
的子序列的末尾值(即当前序列最大值)的最小值
然后可以用反证法证明数组d[]
是一个单调递增数组
那么意义有二:1. 可以用二分法2. 数组d[]
就是最长递增子序列
以输入序列 [0, 8, 4, 12, 2] 为例:
初始化,len=1,d[1] = nums[0] = 0
第二步插入 8,nums[1] = 8 > d[1] = 0,len++=2,d[2] = 8,d = [0, 8]
第三步插入 4,nums[2] = 4 < d[len] = d[2] = 8,
去找第一个比4小的位置,找到1,即d[1] = 0,
更新d[1+1]=d[2] = 4,d = [0, 4]
len不变仍然为2
第四步插入 12,12>d[2] =4,len++=3,d[3] = 12,d = [0, 4, 12]
第五步插入 2,2<d[3] = 12,
去找第一个比2小的位置,d[1] = 0
更新d[1+1] = d[2] = 4 = 2
d = [0, 2, 12]
遍历整个nums数组,遇到比d数组最后一个(最大一个)元素大的,就追加到d数组后面,并更新最大长度len的值
遇到小的,就(通过二分查找在d数组中)找第一个比它还小的,更新这个元素后一个位置的值
class Solution { public: int lengthOfLIS(vector<int>& nums) { int len = 1;// 代表当前最长递增子序列长度 int n = nums.size(); // 省略检查 vector<int> d(n + 1, 0);// 为什么长度是n+1?d[0]没有用,长度为0的子序列没有意义 d[len] = nums[0];// 初始化d[1] // 怎么不考虑d[n]吗?整个nums都是递增的呢?子序列是否包含自身? // 主要是nums[n]非法吗? for (int i = 1; i < n; ++i) { // 遍历元素 if (nums[i] > d[len]) { // 这里更新了len自增 d[++len] = nums[i]; } else { // 如果小于 int left = 1, right = len, pos = 0; // 如果找不到说明所有数都比nums[i]大,此时需要更新d[1],所以将pos设为0 // 二分开始,找第一个比nums[i]小的位置 while (left <= right) { int mid = (left + right) >> 1; // 返回第一个小于 if (d[mid] < nums[i]) { pos = mid; left = mid + 1; } else { right = mid - 1; } d[pos + 1] = nums[i]; } } return len; } } };
本文作者:YaosGHC
本文链接:https://www.cnblogs.com/yaocy/p/16554375.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步