Leetcode题目300.最长上升子序列(动态规划+贪心-中等)

题目描述:

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

示例:

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

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

思路分析:(题解来自:https://leetcode-cn.com/u/liweiwei1419/

第一种是常规解法:动态规划,时间复杂度为 O(N^2);

“动态规划”的两个步骤是思考“状态”以及“状态转移方程”。

有的资料又将“动态规划”分为 3 步:

base case:思考问题规模最小的时候,是什么情况;
update function:自下而上思考这个问题,即上面的“状态转移方程”;
gola:重点强调了输出是什么,很多时候输出并不一定是最后一个状态。
我觉得这种分法更细致一点,“状态”以及“状态转移方程”也没有问题,但是我觉得还要加上一个,思考一下“输出”是什么,即将第 2 种的第 3 步加上去,在下面的分析中,我还会强调这一点。

1、定义状态

首先我们考虑能否将题目的问法定义成状态,即 dp[i] 表示长度为 i 的最长上升子序列的长度,但仔细思考之后,我们发现:由于“子序列”不要求连续,长度为 i - 1 的最长上升子序列,与长度为 i 的“最长上升子序列之间的递推关系并不那么容易得到。

但我们由「力扣」第 3 题:“无重复字符的最长子串”以及「力扣」第 53 题:“最大子序和”这两个问题的经验,再结合题意,可以知道,“上升”的递推关系是:看子序列最后一个数,如果一个新数,比子序列最后一个数还大,那么就可以放在这个子序列的最后,形成一个更长的子序列。反正一个子序列一定会以一个数字结尾,那我就将状态成以 nums[i] 结尾的“最长上升子序列”的长度,这一点是常见的。

dp[i]:表示以第 i 个数字为结尾的“最长上升子序列”的长度。即在 [0, ..., i] 的范围内,选择 以数字 nums[i] 结尾 可以获得的最长上升子序列的长度。注意:以第 i 个数字为结尾,即 要求 nums[i] 必须被选取。

初始化的时候,因为每个元素自己可以认为是一个长度为 1 的子序列,所以可以将 dp 数组的值全部设置为 1。

定义输出:下面要考虑一下输出,由于状态不是题目中的问法,因此不能将最后一个状态作为输出,这里输出是把 dp[0]、dp[1]、……、dp[n - 1] 全部看一遍,取最大值。

2、推导“状态转移方程”

遍历到索引是 i 的数的时候,根据上面“状态”的定义,考虑把 i 之前的所有的数都看一遍,只要当前的数 nums[i] 严格大于之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列。因此,dp[i] 就是之前严格小于 nums[i] 的“状态”最大值加 1。

因此,状态转移方程是:

dp[i] = max{1 + dp[j] for j < i if nums[j] < nums[i]}

代码实现:

class Solution {
       public static int lengthOfLIS(int[] nums) {

        int len = nums.length;
        int[] dp = new int[len];
        Arrays.fill(dp, 1);
        //初始化dp数组,每个元素至少都是以它自身为结尾,长度为1的自序列
        int maxLen = 0;
        //从第二个元素开始
        for (int i = 1; i < len; i++) {
            //以当前元素为结尾
            int curVal = nums[i];
            for (int j = 0; j < i; j++) {
                //当前元素严格大于之前的任何一个片段,则当前元素都可以加在这个区间后面,形成+1长度的自序列
                if (curVal > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        for (int element : dp) {
            maxLen = Math.max(maxLen, element);
        }
        return maxLen;
    }
}

时间复杂度:O(n^2)

空间复杂度:O(n)

第二种方法有一定技巧性,可以认为是贪心算法 + 二分查找,时间复杂度为 O(NlogN),这里 N 表示数组的长度。

猜你喜欢

转载自www.cnblogs.com/ysw-go/p/11933852.html