300. Longest Increasing Subsequence
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?
方法1: dynamic programming
思路:
dp(n , 0): 以 i 为结尾的数组中,LIS是多少。那么转移方程就是对于所有在i之前j 位置上的数字,他们如果nums[j] < nums[i], dp[i] = max(dp[i], dp[j] + 1);如果nums[j] >= nums[i], dp[i] = max(dp[i], dp[j])。初始化应该是dp[0] = 1,i.e.在第一位上至少有长度为1的LIS。第二位开始dp。注意此时dp的含义规定了以i为结尾,那么全局最大就不是dp[n - 1],而需要另外维持一个全局最大。为什么一定要以 i 为结尾呢?否则仅判断nums[j] < nums[i]无法保证 i 可以续接在 j 所记录的那个LIS上。请体会test case。
易错点
- dp的定义以及return值
- 初始化
- 每次要和当前位的dp最大值比较,i.e. max(dp[i], dp[j] + 1),因为前面可能存在众多小于nums[i]的数,要取最大
- 因为3,result每轮i更新一次就行,放在最内层太慢
// [1,3,6,7,9,4,10,5,6]
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int result = 1;
if (nums.size() == 0) return 0;
int n = nums.size();
vector<int> dp(n, 1);
for (int i = 1; i < nums.size(); i++){
for (int j = 0; j < i; j++){
if (nums[j] < nums[i]){
dp[i] = max(dp[i], dp[j] + 1);
}
}
result = max(result, dp[i]);
}
return result;
}
};
方法2:
官解:https://leetcode.com/problems/longest-increasing-subsequence/solution/
Tushar: https://www.youtube.com/watch?v=S9oUiVYEq7E
grandyang: http://www.cnblogs.com/grandyang/p/4938187.html (看讨论)
思路:
建立一个dp,但是不用初始化它的长度,而是随着遍历nums动态的推入元素。每当遍历到nums[i], 我们用binary search查找到它在当前dp数组中应在存在的位置(dp是一个递增的数组),如果比所有元素都大,直接推入dp,使dp长度++;而如果它并没有大于末位元素,我们将用它替代第一个大于等于它的元素。这样在循环终止的时候dp长度,即为我们能获取的LIS。注意这个时候返回的dp可能并不是一个真实存在的IS,但是它的长度却反应了真正LIS。举个例子:
input: [0, 8, 4, 12, 2]
dp: [0]
dp: [0, 8]
dp: [0, 4]
dp: [0, 4, 12]
dp: [0 , 2, 12] which is not the longest increasing subsequence, but length of dp array results in length of Longest Increasing Subsequence.
那么这个dp到底在维护什么?可以这样理解:dp[i]维护的是长度为i+1的递增子序列可以取到的最小结尾值。当替换前面的元素时,会将一个位置上的值尽可能缩小,虽然替换可能破坏了原有的LIS,但是却保留了获得更长LIS的可能性。比如说上面的栗子,遍历到8的时候我们以为长度为2的序列必须以8结尾,但是到4的时候我们就可以选择以4结尾。而之后能够获得的LIS如果可以和[0, 8]续接那么一定可以和[0, 4]续接。2最后替换了4是因为如果后面还有数字,可能以同样的原理接在[0, 2]后面。为什么长度一定是LIS的正确长度呢?因为每次增加dp长度的条件是nums[i]大于之前所有数,这个新的LIS是真实存在的,不管之后会不会被替换。多试几个栗子体会。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp;
for (int i = 0; i < nums.size(); i++){
int left = 0, right = dp.size();
while (left < right){
int mid = left + (right - left) / 2;
if (dp[mid] < nums[i]) left = mid + 1;
else right = mid;
}
if (left >= dp.size()) dp.push_back(nums[i]);
else dp[left] = nums[i];
}
return dp.size();
}
};