算法-动态规划-最长上升子序列

算法-动态规划-最长上升子序列

1 题目概述

1.1 题目出处

https://leetcode-cn.com/problems/longest-increasing-subsequence/

1.2 题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

2 动态规划

2.1 思路

如果设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]

2.2 代码

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;
    }
}

2.3 时间复杂度

算法-动态规划-最长上升子序列_第1张图片
O(N^2)

2.4 空间复杂度

O(N)

3 有序数组+二分查找

3.1 思路

目标将算法的时间复杂度降低到 O(n log n) ,那么能想到的是二分或者归并之类的。

但是数组本身无需,要找最长上升子序列,不可能先排序再找吧?

而dp[i]也是无序的,也无法使用二分查找。

有一种思路,使用一个有序数组。当遍历到一个元素大于该数组的尾元素(最大的),就放置在末尾;否则就使用二分查找,如果找到就不动,找不到就替换比目标元素大的右边那个元素。这样替换的依据是,已经用末尾最大元素限制了长度,更小的元素只能替换而不能增加该序列长度!

这样的好处是,该有序数组长度就表示了最长上升子序列长度,而且复杂度优化到O(NlogN)!

3.2 代码

3.2.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{
                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);
            }
        }
    }
}

3.2.2 循环版本

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();
    }
}

3.3 时间复杂度

3.3.1 递归版本

在这里插入图片描述
O(NlogN)

3.3.2 循环版本

在这里插入图片描述
O(NlogN)

3.4 空间复杂度

O(K)

  • 取决于最长子序列长度K

你可能感兴趣的:(算法)