最长递增子序列的变种:带限制的最长递增子序列(每个元素最多出现k次)
字数 1285 2025-11-03 18:00:43

最长递增子序列的变种:带限制的最长递增子序列(每个元素最多出现k次)

题目描述:
给定一个整数数组nums和一个整数k,要求找出最长递增子序列的长度,其中子序列中每个元素最多出现k次。注意:这里的子序列不要求连续,但必须保持原数组中的相对顺序。

举例说明:
nums = [1, 2, 2, 3, 3, 3, 4], k = 2
最长递增子序列可以是[1, 2, 2, 3, 3, 4],长度为6

解题思路:
这个问题是标准最长递增子序列(LIS)问题的变种,增加了每个元素出现次数不超过k次的限制。我们需要修改标准的动态规划解法来处理这个限制。

详细解题步骤:

  1. 状态定义:
    定义dp[i]表示以第i个元素结尾的最长递增子序列的长度。

  2. 限制条件处理:
    由于每个元素最多出现k次,我们需要记录在当前位置之前,当前元素已经出现了多少次。因此我们需要一个辅助数组count来记录。

  3. 状态转移方程:
    对于每个位置i,我们需要:

  • 遍历所有j < i,如果nums[j] < nums[i],那么可以考虑将nums[i]接在以nums[j]结尾的子序列后面
  • 但是需要检查:如果nums[j] == nums[i],那么我们需要确保在j到i的区间内,nums[i]出现的次数不超过k-1次(因为加上当前这个就是第k次)

更精确的状态转移:
dp[i] = max(1, max{dp[j] + 1 | j < i, nums[j] < nums[i], 且满足出现次数限制})

  1. 出现次数限制的具体实现:
    我们可以维护一个lastOccurrence数组来记录每个数值最近出现的k个位置,或者使用滑动窗口的思想。

  2. 算法优化:
    直接的双重循环时间复杂度是O(n²),但我们可以优化:

  • 使用二分查找来加速
  • 维护一个有序数据结构来快速找到满足条件的j
  1. 具体实现细节:
    我们可以为每个不同的数值维护一个列表,记录该数值出现的位置,然后使用动态规划结合这些信息。

让我用一个完整的例子来演示:

示例:nums = [1, 2, 2, 3, 3, 3, 4], k = 2

步骤1:初始化
dp = [1, 1, 1, 1, 1, 1, 1]
count = 记录每个数值出现次数的字典

步骤2:逐个处理每个元素:

  • 位置0:数值1,dp[0] = 1
  • 位置1:数值2,检查前面所有小于2的数:只有1,dp[1] = dp[0] + 1 = 2
  • 位置2:数值2,检查前面:
    • 数值1:dp[0] + 1 = 2
    • 数值2:但数值2已经出现1次,还可以再出现1次,所以dp[2] = dp[1] + 1 = 3
  • 位置3:数值3,检查前面小于3的数:1和2
    • 从数值1:dp[0] + 1 = 2
    • 从数值2:dp[1] + 1 = 3, dp[2] + 1 = 4
    • dp[3] = 4
  • 以此类推...

最终得到最长递增子序列长度为6。

这个问题的关键在于正确处理每个数值的出现次数限制,确保在构建递增子序列时不超过k次的限制。

最长递增子序列的变种:带限制的最长递增子序列(每个元素最多出现k次) 题目描述: 给定一个整数数组nums和一个整数k,要求找出最长递增子序列的长度,其中子序列中每个元素最多出现k次。注意:这里的子序列不要求连续,但必须保持原数组中的相对顺序。 举例说明: nums = [ 1, 2, 2, 3, 3, 3, 4 ], k = 2 最长递增子序列可以是[ 1, 2, 2, 3, 3, 4 ],长度为6 解题思路: 这个问题是标准最长递增子序列(LIS)问题的变种,增加了每个元素出现次数不超过k次的限制。我们需要修改标准的动态规划解法来处理这个限制。 详细解题步骤: 状态定义: 定义dp[ i ]表示以第i个元素结尾的最长递增子序列的长度。 限制条件处理: 由于每个元素最多出现k次,我们需要记录在当前位置之前,当前元素已经出现了多少次。因此我们需要一个辅助数组count来记录。 状态转移方程: 对于每个位置i,我们需要: 遍历所有j < i,如果nums[ j] < nums[ i],那么可以考虑将nums[ i]接在以nums[ j ]结尾的子序列后面 但是需要检查:如果nums[ j] == nums[ i],那么我们需要确保在j到i的区间内,nums[ i ]出现的次数不超过k-1次(因为加上当前这个就是第k次) 更精确的状态转移: dp[ i] = max(1, max{dp[ j] + 1 | j < i, nums[ j] < nums[ i ], 且满足出现次数限制}) 出现次数限制的具体实现: 我们可以维护一个lastOccurrence数组来记录每个数值最近出现的k个位置,或者使用滑动窗口的思想。 算法优化: 直接的双重循环时间复杂度是O(n²),但我们可以优化: 使用二分查找来加速 维护一个有序数据结构来快速找到满足条件的j 具体实现细节: 我们可以为每个不同的数值维护一个列表,记录该数值出现的位置,然后使用动态规划结合这些信息。 让我用一个完整的例子来演示: 示例:nums = [ 1, 2, 2, 3, 3, 3, 4 ], k = 2 步骤1:初始化 dp = [ 1, 1, 1, 1, 1, 1, 1 ] count = 记录每个数值出现次数的字典 步骤2:逐个处理每个元素: 位置0:数值1,dp[ 0 ] = 1 位置1:数值2,检查前面所有小于2的数:只有1,dp[ 1] = dp[ 0 ] + 1 = 2 位置2:数值2,检查前面: 数值1:dp[ 0 ] + 1 = 2 数值2:但数值2已经出现1次,还可以再出现1次,所以dp[ 2] = dp[ 1 ] + 1 = 3 位置3:数值3,检查前面小于3的数:1和2 从数值1:dp[ 0 ] + 1 = 2 从数值2:dp[ 1] + 1 = 3, dp[ 2 ] + 1 = 4 dp[ 3 ] = 4 以此类推... 最终得到最长递增子序列长度为6。 这个问题的关键在于正确处理每个数值的出现次数限制,确保在构建递增子序列时不超过k次的限制。