给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入:[10,9,2,5,3,7,101,18]
输出: 4 解释: 最长的上升子序列是[2,3,7,101],
它的长度是4
。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
解题思路:
1. 普通算法
动态规划。假设数组的前n个数的所有上升子序列是vector<vector<int>> data。第n+1个数将会对data产生影响。1. 如果data[i-1].back()<nums[n+1],那么将nums[n+1]加入数组末尾。2.由于最终的结果未必包含nums[n+1],于是当加入nums[n+1]时需要将data[i-1]复制一份加入data。这样表面上解决了问题,但是实际上如果每次都需要将nums[n+1]加入data中所有序列的后端(nums升序),那么data的规模将指数上升,明显是不可取的。
后来一想,将nums[n+1]加入data的所有可能序列的末端,这个操作有很大的改进空间,因为nums[n+1]出现之后,以nums[n+1]结尾的最长升序是确定的而且是唯一的,取决于data中升序列末端小于nums[n+1]的最长一个,按照这个想法就可以很好地解决上述的内存不足的现象。
很显然data这个结构已经不适用了,改用可以自动排序的哈希容器map<int,int>mp。其中容器的key代表数字nums,val代表当前以数字nums结尾出现的最长升序列。当遇到nums[n+1]时,只需访问key比nums[n+1]小的pair,在这些key中找一个val最大的,然后当前nums[n+1]结尾的最大升序长度即是val+1。如果没有比nums[n+1]跟小的key,令nums[n+1]=1。如此,mp.size()<=nums.size(),取决于nums中不重复元素的个数,空间复杂度O(n)。时间复杂度O(n)-O(n^2)之间。已经满足了题目要求。
事实上,这个算法测试时间24ms,击败48%,仍有改进的空间。
2. 高级算法
动态规划,二分查找。假设vector<int> data是前n个数查找得到的最长升序列。当nums[n+1]出现时,可能有以下三种情况:
- nums[n+1]是data中的一个数,那么nums[n+1]不会影响原有的升序列。
- nums[n+1]恰好小于data[i],那么data[i]=nums[n+1]。原有的升序列长度不变,只需要理解为什么更新data[i]的值。我们假设最终结果里n+1之后升序列是n_val,如果满足(data[i],n_val)是升序,那么(nums[n+1],n_val)也必然是升序,这个时候讲nums[n+1]替换data[i]这一步是否存在不重要,可有可无,因为求解的是长度。如果不满足(data[i],n_val)是升序,那么,必须替将data[i]换nums[n+1],(nums[n+1],n_val)才是升序。综上,应该替换,但是由此可知,这这么一来,想要求解升序列是什么就难了。
- 剩下只可能是nums[n+1]大于data中的最后一个数,在末尾追加即可。
- 以上的查找,都用二分查找,特殊之处在于返回1,2,3三种情况。
这样一来时间复杂度就成了O(n)-O(nlogn),空间复杂度为O(n)。
普通解法:
class Solution { public: int lengthOfLIS(vector<int>& nums) { int size = nums.size(); if (size <= 1) return size; map<int, int> mp = { {nums[0],1} }; int i; map<int, int>::iterator it; for (i = 2; i <= size; i++) { int max = 0;//获取nums[i-1]的最长序列 bool sgn = false; for (it = mp.begin(); it != mp.end(); it++) { if (it->first < nums[i - 1]) { sgn = true; max = (it->second + 1>max ? it->second + 1 : max); } else break; } if (sgn == false) { mp[nums[i - 1]] = 1; } else mp[nums[i - 1]] = max; } int res=0; for (it = mp.begin(); it != mp.end(); it++) { res = (res > it->second ? res : it->second); } return res; } }; |
参考的高级解法:
class Solution { public: int lengthOfLIS(vector<int>& nums) { int size = nums.size(); vector<int> dp; for (int i = 0; i < size; i++) { int index = binarySearch(dp, nums[i]); if (index == dp.size()) { dp.push_back(nums[i]); } else if (dp[index] > nums[i]) { dp[index] = nums[i]; } } return dp.size(); } int binarySearch(const vector<int>& nums, int target) { int low = 0, high = nums.size(); while (low < high) { int mid = low + (high - low) / 2; if (nums[mid] == target) return mid; else if (nums[mid] < target) low = mid + 1; else high = mid; } return low; } }; |