Given an unsorted array of integers, find the length of longest increasing subsequence.
Example:
Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
Note:
- There may be more than one LIS combination, it is only necessary for you to return the length.
- Your algorithm should run in O(n2) complexity.
Follow up: Could you improve it to O(n log n) time complexity?
解法一,O(n2):
直观想一下,选中一个元素,以这个元素为结尾的子序列前面有多少个比他值小的元素,那么以它为结尾的上升子序列就是多少再加一。即当前状态可以由之前的一个或一些状态推导出来,所以可以用动态规划。建立一个一维数组dp,dp[i]表示以nums[i]结尾的最长上升子序列长度。初始都置为1。对于原数组每个元素,二重循环从头遍历原数组,每当找到一个比当前元素小的值,证明至少可以形成一个dp[j]+1的上升子序列,所以dp[i] = max(dp[i], dp[j] + 1),而dp[j]之前已经求得。
解法二,O(n log n):
还是想一下“人工”做这个题是什么过程。按照上面的例子来分析:
首先看到10,加入备选集,备选集合为{10};
之后看到了9,没有形成上升序列,那么9不应该加入备选集合。但是因为9小于10,所以如果把10替换成9会增加接下来产生上升序列的机会,且并不影响备选集合元素的个数(因为是替换),所以替换掉,备选集现在有{9};
遇到2道理同上,替换掉9,备选集变成{2};
遇到5,这时候形成了上升序列,此时应该是添加到备选集合,变为{2,5};
遇到3,没有形成上升序列,但还是道理同加入9的情况,如果此时把5替换成3,会增加接下来形成上升序列的机会,且备选集保持上升,并且个数也没变,所以替换掉5,备选集变成{2,3};
遇到7,同遇到5,添加元素,备选集{2,3,7};
遇到101,同上,备选集{2,3,7,101};
遇到18,还是一样,虽然没有形成上升序列,但是如果把101替换掉,那么接下来形成上升序列的机会会增加,并且备选集的上升属性和元素个数都不变,所以替换,备选集变为{2,3,7,18}。
至此所有元素添加完毕,备选集的元素个数就是最长上升子序列长度。但这里注意,备选集里面的元素并不是最后最长子序列的元素。因为在寻找最长子序列的过程中,目标是尽可能的让以后形成上升序列的机会增加,所以进行了替换。
“人工”做出来之后,只要用程序实现思考过程就好。总结起来就是:
如果遇到的元素比备选集合里面的元素都大,那么就添加进去,使得上升序列长度增加;
如果遇到的元素比备选集合里最后一个元素小,那么代表它无法被添加到备选集。但是为了使后面得到上升序列的机会增加,需要在不破坏集合上升属性和元素总数的情况下,替换掉备选集中的元素,那么就是替换掉大于他的元素中最小的那个,这样才能满足条件。
这时候,发现备选集一直是保持有序,寻找替换元素的时候就可以用到二分查找,得到O(n log n)的时间复杂度。其中还要注意的是如果元素已经在备选集合中,是不需要任何操作的,因为它并不能增加上升序列的长度,也不会增加之后遇到上升序列的机会,所以直接跳过。
解法一(Java)
class Solution { public int lengthOfLIS(int[] nums) { if (nums == null || nums.length == 0) return 0; int[] dp = new int[nums.length]; int max = 1; for (int i = 0; i < nums.length; i++) dp[i] = 1; for (int i = 0; i < nums.length; i++) { for (int j = 0; j <= i; j++) { if (nums[j] < nums[i]) { dp[i] = Math.max(dp[i], 1 + dp[j]); max = max > dp[i] ? max : dp[i]; } } } return max; }
解法二(Java)
class Solution { public int lengthOfLIS(int[] nums) { if (nums == null || nums.length == 0) return 0; List<Integer> dp = new ArrayList<>(); dp.add(nums[0]); for (int i = 1; i < nums.length; i++) { if (dp.contains(nums[i])) continue; else if (nums[i] > dp.get(dp.size()-1)) dp.add(nums[i]); else if (nums[i] < dp.get(dp.size()-1)) { int l = 0, r = dp.size()-1; while (l < r) { int mid = l + (r - l) / 2; if (dp.get(mid) < nums[i]) l = mid + 1; else r = mid; } dp.set(r, nums[i]); } } return dp.size(); } }