https://leetcode-cn.com/problems/longest-increasing-subsequence/
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
如果设dp[i]表示前i个的最长上升子序列长度,那么很难找到他和dp[i-1]等的关系。
而考虑设dp[i]表示以下标i的数字结尾的上升子序列最大长度, 则:
dp[i] = max(dp[0],dp[1],dp[2],...dp[j]...,dp[i-1]) + 1
且 nums[j] < nums[i]
class Solution {
public int lengthOfLIS(int[] nums) {
// 全局最长上升子序列长度
int result = 0;
if(null == nums || nums.length == 0){
return 0;
}
// 如果设dp[i]表示前i个的最长上升子序列长度
// 那么很难找到他和dp[i-1]等的关系
// 考虑设dp[i]表示以下标i的数字结尾的上升子序列最大长度
// 则dp[i] = max(dp[0],dp[1],dp[2],...dp[j]...,dp[i-1]) + 1,且 nums[j] < nums[i]
int[] dp = new int[nums.length];
for(int i = 0; i < nums.length; i++){
// 以nums[i]结尾的最长上升子序列长度
int tmpMax = 0;
for(int j = i - 1; j >= 0; j--){
if(nums[j] < nums[i]){
tmpMax = Math.max(tmpMax, dp[j]);
}
}
// 最小长度是1,也就是说前面的数都不小于nums[i],则nums[i]本身组成一个长度为1的子序列
dp[i] = tmpMax + 1;
// 如果比全局更长就更新
result = Math.max(result, dp[i]);
}
return result;
}
}
O(N)
目标将算法的时间复杂度降低到 O(n log n) ,那么能想到的是二分或者归并之类的。
但是数组本身无需,要找最长上升子序列,不可能先排序再找吧?
而dp[i]也是无序的,也无法使用二分查找。
有一种思路,使用一个有序数组。当遍历到一个元素大于该数组的尾元素(最大的),就放置在末尾;否则就使用二分查找,如果找到就不动,找不到就替换比目标元素大的右边那个元素。这样替换的依据是,已经用末尾最大元素限制了长度,更小的元素只能替换而不能增加该序列长度!
这样的好处是,该有序数组长度就表示了最长上升子序列长度,而且复杂度优化到O(NlogN)!
class Solution {
private List<Integer> resultList = new ArrayList<>();
public int lengthOfLIS(int[] nums) {
// 全局最长上升子序列长度
int result = 0;
if(null == nums || nums.length == 0){
return 0;
}
resultList.add(nums[0]);
for(int i = 1; i < nums.length; i++){
if(nums[i] > resultList.get(resultList.size() - 1)){
resultList.add(nums[i]);
}else{
binaryInsert(nums[i], 0, resultList.size() - 1);
}
}
return resultList.size();
}
private void binaryInsert(int target, int start, int end){
int mid = (start + end) / 2;
if(resultList.get(mid) == target){
return;
} else if(resultList.get(mid) < target){
if(mid == end){
// 因为我们已经提前判断过target大于数组尾元素情况,
// 所以这里不会出现end+1不存在的情况
resultList.set(end + 1, target);
}else{
binaryInsert(target, mid + 1, end);
}
}else{
// resultList.get(mid) > target
if(mid == start){
resultList.set(mid, target);
}else{
binaryInsert(target, start, mid - 1);
}
}
}
}
class Solution {
private List<Integer> resultList = new ArrayList<>();
public int lengthOfLIS(int[] nums) {
// 全局最长上升子序列长度
int result = 0;
if(null == nums || nums.length == 0){
return 0;
}
resultList.add(nums[0]);
for(int i = 1; i < nums.length; i++){
if(nums[i] > resultList.get(resultList.size() - 1)){
resultList.add(nums[i]);
}else{
int left = 0;
int right = resultList.size() - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(resultList.get(mid) == nums[i]){
// 相同值的忽略
break;
} else if (resultList.get(mid) < nums[i]){
left = mid + 1;
} else if (resultList.get(mid) > nums[i]){
right = mid - 1;
}
}
// right == left时,resultList.get(mid) > nums[i]
// 不可能resultList.get(mid) < nums[i]
// 因为我们提前判断了nums[i] > resultList.get(resultList.size() - 1)
// 所以这里我们将目标数字放在left位置即可
if(right < left){
resultList.set(left, nums[i]);
}
}
}
return resultList.size();
}
}
O(K)