导读:算法哥发现把题目难度降低后,阅读量显著上升,所以今天再普及一道动态规划入门题。
题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
题目分析:
O(n^2)算法相信大家都会就不废话了,动态规划!!!定义dp[i]表示已第i个数字结尾的最长递增子序列的长度,则:
dp[i] = max(dp[j] + 1, 0 <= j < i && nums[j]
通估测试的源码如下:
那么怎么把O(n^2)算法优化到O(nlogn)呢?
依然是动态规划,不过这一次,dp[i]的状态需要重新设计一下,
dp[i]表示最长递增子序列长度为i+1时(因为数组下标从0开始的),末尾最小的数
有点绕,先不管,举个例子,你就明白鸟~
假设有数组2,4,3,4,我们依次处理,
首先考虑第一个元素2,此时dp数组里为空,所以直接插入dp数组,dp[0] = 2,表示当前最长递增子序列长度为1时,子序列末尾最小的数是2,此时只有一个数字,当然是2;
下一个数字4,此时我们在dp数组里找到一个位置,使得位置前一个数比4小,后一个数比4大,此时dp里只有一个2,所以找到2的末尾,所以直接插入4,此时dp[1]=4,表示当前最长递增子序列长度为2的时候,末尾最小元素是4;
接下来考虑3,此时依然是在dp数组里找到一个位置,使得位置前一个数比3小,后一个数比3大,所以3应该插入dp数组里4的位置,dp[1]=3,表示当前最长递增子序列长度为2时,末尾最小的元素是3,为什么要把4覆盖掉呢 ?原因很简单,因为2,3比2,4更容易形成更长的递增子序列;
最后在考虑4,此时,4比dp数组里最末尾的3大,所以直接放到dp数组尾部即可,即dp[2]=4;
最后,最长递增子序列的长度就是dp数组里元素的个数!!!
我们每次考虑一个元素时,都是找到一个位置,使得前一个数比它小,后一个比它大,然后覆盖后一个元素(假设这个元素下标是i),因为我们找到了一个数字,使得最长递增子序列长度不变时(长度i+1),最后一个元素更小了,这样更利于形成更长的最长递增子序列!
显然,dp数组是递增的(每次插入都保证了比前面大,比后面小),而每次查找的过程不就是二分查找吗 ?所以复杂度是O(nlogn)!!!虽然算法哥,每次都是说显然!显然!显然!但是对于初学者,理解起来会有点困难,自己在纸上画画,体会下,哈哈哈
有个偷懒的方法,二分查找直接使用C++的lower_bound干净利落的搞定了!C++真是算法比赛里的大杀器!
总结:
两种方法都可以看到动态规划的影子,永远记住算法哥对动态规划问题的总结:
大问题可以转化成小问题,反过来,小问题可以推导出大问题!!!