最长递增子序列(力扣)图解

本文解析了如何解决LeetCode中关于最长递增子序列的问题,提供了解题思路、详细代码示例,并配以直观图解,助您深入理解动态规划在数组问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最长递增子序列(力扣)

原题

题目链接

题目描述

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

1 <= nums.length <= 2500
-104 <= nums[i] <= 104

解题思路

代码

代码的注释即为思路。(易于理解)。

class Solution {
    public int lengthOfLIS(int[] nums) {
		//如果数组为0个元素则最长递增子序列的个数为0
        if(nums.length==0){
            return 0;
        }else{//当数组的个数不为0
            int[] dp=new int[nums.length];
            //初始化
            for(int i=0;i<nums.length;i++){
            //dp[i]表示以nums[i]结尾的最长递增子序列的个数
                dp[i]=1;
            }
            
            //开始遍历
            //外层循环表示从以nums[i]为最长递增子序列的最后一个元素
            //内层循环从第一个至nums[i]元素(使用下标j进行遍历)
            //在nums[j]<nums[i]情况下(说明以nums[j]结尾的最长增序子序列的最后一个元素小于以nums[i]结尾的最长增序子序列的最后一个元素)
            //所以要看一下是以nums[j]为倒数第二个的序列数长还是不要nums[j]加入的序列长
            for(int i=0;i<nums.length;i++){
                for(int j=0;j<=i;j++){
                    if(nums[j]<nums[i]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                    }
                }
            }

            //找出dp[i]中的最大元素
            int max=0;
            for(int i:dp){
                max=Math.max(max,i);
            }
            return max;
        }

    }
}

图解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<think>好的,用户现在想解决LeetCode上关于寻找长度为k且和最大的子序列的问题。首先,我需要回忆相关的题目和解法。根据用户提供的引用,特别是引用[2]和[5],LeetCode 2099题就是这个问题。用户提到需要算法或解题思路,所以我要先整理已有的信息。 首先,子序列和子数组不同,子序列不要求连续,但顺序必须保持。题目要求长度为k,且和最大。常见的思路可能是先排序然后取最大的k个数,但这样会破坏顺序,导致得到的子序列顺序与原数组不符。比如,如果数组是[3,1,4,2],k=2,排序后最大的两个数是4和3,但原数组中4在3之后,所以正确的子序列应该是[3,4],但直接排序选最大的两个可能得到[4,3],顺序不对。因此,直接排序不行,需要考虑其他方法。 引用[5]中提到了选择排序的方法,每次选出剩余元素中的最大值,并记录其索引,然后将该位置置为None,避免重复选择。最后根据记录的索引排序,得到正确顺序的子序列。这种方法的时间复杂度是O(nk),当k较小时比较高效。但若k接近n,则可能达到O(),不过题目可能数据规模不大,可以接受。 另一种方法是使用堆(优先队列),如引用[2]提到的堆解法。具体来说,可以维护一个大小为k的最大堆,或者使用堆来快速找到最大的k个元素及其索引。不过需要注意保持元素的顺序。可能的步骤是:遍历数组,将元素和索引存入堆中,按值从大到小排序。取出前k个元素后,根据索引排序,得到正确顺序的子序列。这种方法的时间复杂度是O(n log n),因为堆的插入操作是O(log n),而最后对k个元素排序是O(k log k)。对于较大的n,这种方法可能更优。 然后,我需要考虑动态规划是否有适用的可能,但根据问题特点,子序列需要保持顺序,并且长度固定为k,和最大。动态规划的状态可能设计为dp[i][j],表示前i个元素中长度为j的子序列的最大和。状态转移方程需要考虑是否选择当前元素。但这样的动态规划时间复杂度是O(nk),可能和选择排序方法相当,但实现起来稍微复杂。不过引用中没有提到动态规划解法,可能更推荐堆或选择方法。 现在需要比较这两种方法的优劣。堆方法的时间复杂度更低,尤其是当k较大时。而选择排序方法在k较小时更优,实现简单。根据题目给出的示例代码,引用[5]中的解法就是选择排序的思路,每次选最大的元素,记录索引,最后按索引排序输出。这可能更适合快速实现,尤其是在编程比赛中。 另外,还需要注意重复元素的情况。例如,如果有多个相同的最大值,需要正确选择它们的索引,以保持子序列的顺序。例如数组[5,3,5,2],k=2,应选择第一个5和第二个5,子序列是[5,5],但它们的索引分别是0和2,所以最终输出应为[5,5]。 总结可能的解题步骤: 1. 遍历数组找到最大的k个元素,并记录它们的值和原始索引。 2. 根据记录的索引进行排序,确保子序列的顺序与原数组一致。 3. 提取对应的值形成结果子序列。 在实现时,需要注意如何处理元素的选择,确保每次选的是当前剩余元素中的最大值,同时记录索引。引用[5]中的代码通过将已选元素置为None来避免重复选择,这可能有效,但修改原数组是否允许需要考虑题目要求。如果题目不允许修改原数组,可能需要复制数组或使用其他方式标记已选元素。 另外,堆方法如何实现?例如,可以将所有元素和索引一起存入堆中,按值从大到小排序。取出前k个元素后,根据索引排序,然后输出对应的值。这样可以避免修改原数组,并且时间复杂度为O(n + k log k),因为堆化需要O(n)时间(如果使用heapq.nlargest),然后对k个元素排序需要O(k log k)。这可能更高效,尤其是当n较大时。 因此,用户的问题需要给出这两种可能的解法,并比较它们的优缺点。同时,引用中的示例代码使用的是选择排序的方法,而引用[2]提到了堆解法,所以需要详细说明这两种方法,并给出代码示例。 另外,需要注意子序列和子数组的区别,确保解答中明确说明这一点。例如,子序列不需要连续,但顺序必须保持,而子数组是连续的。 最后,根据用户提供的引用,特别是引用[2]和[5],需要将这两种方法整合到回答中,并正确引用对应的参考资料。</think>### LeetCode 2099. 找到和最大的长度为 K 的子序列 #### 问题分析 题目要求从整数数组 $nums$ 中选出长度为 $k$ 的子序列,使得其和为最大,且保持元素的原始顺序[^2]。由于子序列不要求连续,但必须保持顺序,直接排序会破坏索引关系,因此需要结合数值大小和索引顺序进行选择。 --- ### 核心解法 #### 1. 选择排序法(贪心思想) **思路**:每次选择当前剩余元素中的最大值,记录其索引,并将该位置标记为已选(例如置为 `None`),重复此过程直到选满 $k$ 个元素。最后根据索引顺序输出结果。 - **时间复杂度**:$O(nk)$,适用于 $k$ 较小的情况。 - **空间复杂度**:$O(1)$(原地修改数组)或 $O(n)$(使用辅助数组)。 ```python def maxSubsequence(nums, k): ans = [] for _ in range(k): max_val, max_idx = -float('inf'), -1 for idx, val in enumerate(nums): if val is not None and val > max_val: max_val, max_idx = val, idx ans.append((max_idx, max_val)) nums[max_idx] = None # 标记已选元素 # 按索引排序后提取值 return [val for idx, val in sorted(ans)] ``` --- #### 2. 堆(优先队列)优化法 **思路**:利用最大堆快速获取前 $k$ 个最大元素及其索引,再按索引排序恢复原始顺序。 - **时间复杂度**:$O(n \log n + k \log k)$,适合 $k$ 较大的情况。 - **空间复杂度**:$O(n)$。 ```python import heapq def maxSubsequence(nums, k): # 将元素和索引存入堆,按值降序排列 heap = [(-val, idx) for idx, val in enumerate(nums)] heapq.heapify(heap) # 取前k个元素(值最大的k个) selected = [] for _ in range(k): val, idx = heapq.heappop(heap) selected.append((idx, -val)) # 按索引排序后提取值 selected.sort() return [val for idx, val in selected] ``` --- ### 关键点对比 | 方法 | 适用场景 | 时间复杂度 | 空间复杂度 | |--------------|------------------|------------------|------------| | 选择排序法 | $k$ 较小 | $O(nk)$ | $O(1)$ | | 堆优化法 | $k$ 较大或通用 | $O(n \log n)$ | $O(n)$ | --- ### 典型用例 1. **输入**:$nums = [3,1,4,2], k = 2$ **输出**:$[3,4]$(子序列索引为0和2,和为7) 2. **输入**:$nums = [5,3,5,2], k = 2$ **输出**:$[5,5]$(索引0和2,和为10) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

齊 天 大 聖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值