求解最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

相似题目:求解整型数组的最大子串和

求解最长递增子序列,说明这是一个“最优化”问题,并且该问题可以采用动态规划求解。
假设 dp[i] 表示数组 nums[0...i] 的最长递增子序列的长度,要注意:nums[i] 不一定是 dp[i] 所代表的最长递增子序列中的一个元素。

比如数组 [0,1,2,-1] nums[3]=-1 并不是 dp[3] 所代表的最长递增子序列中的元素。dp[3] 所代表的最长递增子序列是:[0,1,2],因为有3个元素,所以长度为3,即:dp[3]=3

要想求解 dp[i],显然 dp[0]=1
i=1,求解:dp[1],如果 nums[1] 大于 nums[0],则 dp[1]=dp[0]+1
i=2, 求解:dp[2],遍历 nums[0]、nums[1],即 j = {0,1},如果 nums[2] 比 nums[j] 要大,nums[2] 可以作为某个序列的最后一个元素,但是最大的序列长度需要遍历j = {0,1} 取最大值,即: dp[2] = max{dp[j]+1, dp[i]}
i=3, 求解:dp[3],遍历 nums[0]、nums[1]、nums[2],即 j={0,1,2},如果 如果 nums[3] 比 nums[j] 要大,nums[3] 可以作为某个序列的最后一个元素,但是最大的序列长度需要遍历j = {0,1,2} 取最大值,即: dp[3] = max{dp[j]+1, dp[i]}
...
如果最长递增子序列以 nums[i] 结尾,那么 nums[0,1...i-1] 中所有比 nums[i] 小的元素,都可以作为该序列的倒数第2个数。在这么多个比 nums[i] 小的元素中,以哪个数结尾的最大递增子序列更大,就选这个数作为倒数第2个数。也即:
dp[i] = max{dp[j]+1, dp[i]} 其中: j = 0,1,2,...i-1

代码如下:

    public int lengthOfLIS(int[] nums) {
        //dp[i] 代表 nums[0,1...i] 0到i 个元素的最长递增子序列的长度
        int[] dp = new int[nums.length];
        //nums数组 第0个元素的最长递增子序列就是它自己, 因为长度为1
        dp[0] = 1;
        for (int i = 1; i < nums.length; i++) {
            //why?
            dp[i] = 1;
            //遍历 [0... i-1] 元素
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    //说明针对位置 j 的元素, 可以将第 i 个元素加入, 去形成递增子序列, 但是 nums[j] 和 nums[i] 能不能形成最长子序列呢?答案是不一定
                    //求解 dp[i], 需要知道 dp[0] dp[1] ... dp[i-1], 找出最大的那个 dp[j]
                    //最大的那个 dp[j] 可以作为: dp[i] 代表的最大递增子序列的 倒数第2个元素
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }
        }
        int maxIndex = findSeqLenMaxIndex(dp);
        return dp[maxIndex];
    }

    private int findSeqLenMaxIndex(int[] arr) {
        int maxIndex = arr.length - 1;
        int maxEle = arr[arr.length - 1];
        for (int i = arr.length - 1; i >= 0; i--) {
            if (arr[i] > maxEle) {
                maxIndex = i;
                maxEle = arr[i];
            }
        }
	//数组 arr 中最大元素的 下标
        return maxIndex;
    }

由于 dp 数组记录的是最长递增子序列的长度。我们也可以进一步得到:最长子序列。
根据上面示例数组 [0,3,1,6,2,2,7] 的最长子序列长度求得的 dp 数组是:dp=[1, 1, 1, 2, 2, 3, 4, 4]

我们从 dp 数组的最后一个元素开始,寻找 dp 数组中最大值,然后针对最大值所在的下标 maxIndex,往前遍历,如果:
nums[j] < nums[maxIndex] && dp[j] == dp[i]-1,说明 nums[j] 可以作为最长递增子序列的倒数第2个数。
生成最长递增子序列的代码如下:

    private int[] generateLIS(int[] nums, int[] dp) {
        int seqLenMaxIndex = findSeqLenMaxIndex(dp);
        //最大递增子序列的 长度
        int seqLen = dp[seqLenMaxIndex];
	//存储 最大递增子序列的 元素
        int[] maxSeqArr = new int[seqLen];
	//最长递增子序列 的最后一个元素
        maxSeqArr[--seqLen] = nums[seqLenMaxIndex];

        for (int i = seqLenMaxIndex - 1; i >= 0; i--) {
            if (nums[i] < nums[seqLenMaxIndex] && dp[i] == dp[seqLenMaxIndex] - 1) {
                maxSeqArr[--seqLen] = nums[i];
		//最长递增子序列 的前一个最大元素的 下标
                seqLenMaxIndex = i;
            }
        }
        return maxSeqArr;
    }
posted @   大熊猫同学  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示