最长递增子序列的变种:带限制的最长递增子序列(每个元素最多出现k次)
字数 1442 2025-11-06 22:52:24

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

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

例如:
nums = [1, 2, 2, 3, 3, 3, 4], k = 2
最长递增子序列为[1, 2, 2, 3, 3, 4],长度为6(其中2出现2次,3出现2次,都满足最多出现k=2次的限制)

解题思路

这个问题是经典最长递增子序列(LIS)问题的变种,增加了每个元素出现次数不超过k次的限制。我们需要在动态规划状态中记录元素出现的次数信息。

详细解题步骤

步骤1:定义状态
定义dp[i][c]表示以第i个元素结尾,且该元素在子序列中出现次数为c时的最长递增子序列长度。

由于数组可能很大,我们需要优化状态表示。实际上,我们可以按元素值分组,对每个不同的数值维护其出现位置。

步骤2:状态转移
对于每个位置i,考虑所有可能的出现次数c(1 ≤ c ≤ k):

  1. 如果c = 1,表示这是该元素第一次出现在子序列中
    dp[i][1] = 1 + max{dp[j][c']} 对于所有j < i且nums[j] < nums[i],c' ≤ k

  2. 如果c > 1,表示该元素已经出现过c-1次
    我们需要找到前一个相同元素的位置p,且该位置的出现次数为c-1
    dp[i][c] = dp[p][c-1] + 1(如果存在这样的p)

步骤3:优化实现
直接实现上述状态转移会超时,我们需要优化:

  • 对每个不同的数值,记录其所有出现位置
  • 使用树状数组或线段树来快速查询前缀最大值

步骤4:具体算法

  1. 预处理:记录每个数值的所有出现位置
  2. 初始化dp数组为0
  3. 按顺序遍历数组:
    • 对于每个位置i,考虑所有出现次数c(1到k)
    • 如果c=1:查询所有小于nums[i]的数值对应的最大dp值
    • 如果c>1:找到nums[i]的前一个出现位置(第c-1次出现)
    • 更新dp[i][c]
    • 更新数据结构

步骤5:最终答案
答案是所有dp[i][c]中的最大值

举例说明
以nums = [1, 2, 2, 3, 3, 3, 4], k = 2为例:

处理过程:

  • 位置0(值1):dp[0][1] = 1
  • 位置1(值2):dp[1][1] = max(0, dp[0][1]) + 1 = 2
  • 位置2(值2):dp[2][1] = max(0, dp[0][1]) + 1 = 2
    dp[2][2] = dp[1][1] + 1 = 3
  • 位置3(值3):dp[3][1] = max(dp[0..2]) + 1 = 4
  • 位置4(值3):dp[4][1] = max(dp[0..2]) + 1 = 4
    dp[4][2] = dp[3][1] + 1 = 5
  • 位置5(值3):dp[5][2] = dp[4][1] + 1 = 5(不能有第3次出现,因为k=2)
  • 位置6(值4):dp[6][1] = max(dp[0..5]) + 1 = 6

最终答案为6,对应子序列[1, 2, 2, 3, 3, 4]。

这个算法的时间复杂度是O(nk logM),其中M是数值范围,可以通过离散化优化到O(nk logn)。

最长递增子序列的变种:带限制的最长递增子序列(每个元素最多出现k次) 题目描述 给定一个整数数组nums和一个整数k,找到最长递增子序列的长度,要求这个子序列中每个元素最多出现k次。注意:子序列不要求连续,但必须保持原数组中的相对顺序。 例如: nums = [ 1, 2, 2, 3, 3, 3, 4 ], k = 2 最长递增子序列为[ 1, 2, 2, 3, 3, 4 ],长度为6(其中2出现2次,3出现2次,都满足最多出现k=2次的限制) 解题思路 这个问题是经典最长递增子序列(LIS)问题的变种,增加了每个元素出现次数不超过k次的限制。我们需要在动态规划状态中记录元素出现的次数信息。 详细解题步骤 步骤1:定义状态 定义dp[ i][ c ]表示以第i个元素结尾,且该元素在子序列中出现次数为c时的最长递增子序列长度。 由于数组可能很大,我们需要优化状态表示。实际上,我们可以按元素值分组,对每个不同的数值维护其出现位置。 步骤2:状态转移 对于每个位置i,考虑所有可能的出现次数c(1 ≤ c ≤ k): 如果c = 1,表示这是该元素第一次出现在子序列中 dp[ i][ 1] = 1 + max{dp[ j][ c']} 对于所有j < i且nums[ j] < nums[ i ],c' ≤ k 如果c > 1,表示该元素已经出现过c-1次 我们需要找到前一个相同元素的位置p,且该位置的出现次数为c-1 dp[ i][ c] = dp[ p][ c-1 ] + 1(如果存在这样的p) 步骤3:优化实现 直接实现上述状态转移会超时,我们需要优化: 对每个不同的数值,记录其所有出现位置 使用树状数组或线段树来快速查询前缀最大值 步骤4:具体算法 预处理:记录每个数值的所有出现位置 初始化dp数组为0 按顺序遍历数组: 对于每个位置i,考虑所有出现次数c(1到k) 如果c=1:查询所有小于nums[ i ]的数值对应的最大dp值 如果c>1:找到nums[ i ]的前一个出现位置(第c-1次出现) 更新dp[ i][ c ] 更新数据结构 步骤5:最终答案 答案是所有dp[ i][ c ]中的最大值 举例说明 以nums = [ 1, 2, 2, 3, 3, 3, 4 ], k = 2为例: 处理过程: 位置0(值1):dp[ 0][ 1 ] = 1 位置1(值2):dp[ 1][ 1] = max(0, dp[ 0][ 1 ]) + 1 = 2 位置2(值2):dp[ 2][ 1] = max(0, dp[ 0][ 1 ]) + 1 = 2 dp[ 2][ 2] = dp[ 1][ 1 ] + 1 = 3 位置3(值3):dp[ 3][ 1] = max(dp[ 0..2 ]) + 1 = 4 位置4(值3):dp[ 4][ 1] = max(dp[ 0..2 ]) + 1 = 4 dp[ 4][ 2] = dp[ 3][ 1 ] + 1 = 5 位置5(值3):dp[ 5][ 2] = dp[ 4][ 1 ] + 1 = 5(不能有第3次出现,因为k=2) 位置6(值4):dp[ 6][ 1] = max(dp[ 0..5 ]) + 1 = 6 最终答案为6,对应子序列[ 1, 2, 2, 3, 3, 4 ]。 这个算法的时间复杂度是O(nk logM),其中M是数值范围,可以通过离散化优化到O(nk logn)。