最长递增子序列的变种:带限制的最长递增子序列(每个元素最多出现k次)
题目描述
给定一个整数数组nums和一个整数k,找到最长递增子序列的长度,要求这个子序列中每个元素最多出现k次。注意:子序列不要求连续,但必须保持原数组中的相对顺序。
示例:
输入:nums = [1,2,2,3,3,3,4], k = 2
输出:4
解释:最长递增子序列可以是[1,2,2,3]或[1,2,3,3]等,其中每个数字最多出现2次。
解题思路
这个问题是标准最长递增子序列(LIS)问题的变种,增加了每个元素出现次数不超过k次的限制。我们需要修改传统的动态规划方法来解决这个限制。
详细解题步骤
步骤1:定义状态
我们定义dp[i]为以第i个元素结尾的最长递增子序列的长度。但为了处理出现次数限制,我们需要记录每个值出现的次数。
更准确地说,我们定义:
- dp[i]:以nums[i]结尾的最长递增子序列长度
- count[i]:在dp[i]对应的子序列中,nums[i]出现的次数
步骤2:状态转移方程
对于每个位置i,我们需要检查所有j < i:
- 如果nums[j] < nums[i],那么可以将nums[i]接在以nums[j]结尾的子序列后面
- 但是需要检查添加nums[i]后,整个子序列中nums[i]的出现次数是否超过k
状态转移方程:
dp[i] = max{ dp[j] + 1 },对于所有j < i且满足:
- nums[j] < nums[i]
- 在以j结尾的子序列中,nums[i]的出现次数 < k
步骤3:处理出现次数限制
为了跟踪每个值在子序列中的出现次数,我们需要维护一个辅助数组:
- freq[i][val]:记录在以i结尾的子序列中,值val出现的次数
或者更高效的方法是:对于每个位置i,我们维护一个映射,记录当前子序列中各个数值的出现次数。
步骤4:算法实现
我们可以使用动态规划结合出现次数跟踪:
- 初始化dp数组全为1(每个元素单独构成子序列)
- 初始化freq数组,记录每个位置对应的子序列中数值出现情况
- 遍历数组,对于每个位置i:
- 初始化dp[i] = 1
- 初始化freq[i]中,nums[i]出现1次
- 对于所有j < i:
- 如果nums[j] < nums[i]:
- 检查freq[j]中nums[i]的出现次数
- 如果出现次数 < k,则可以接上:dp[i] = max(dp[i], dp[j] + 1)
- 更新freq[i]中各个数值的出现次数
- 如果nums[j] < nums[i]:
步骤5:优化考虑
上述方法的时间复杂度是O(n²),对于较大的n可能效率不高。我们可以考虑优化:
优化思路:使用二分查找+贪心思想,类似LIS的O(n log n)解法,但需要修改以处理出现次数限制。
步骤6:最终解法
更高效的解法是维护多个"桶",每个桶对应不同的子序列长度,对于每个长度,我们记录可能的最小结尾值及其出现次数信息。
具体实现:
- 使用一个列表tails,其中tails[i]表示长度为i+1的所有递增子序列的最小结尾值
- 对于每个数值,我们需要维护它在当前子序列中的出现次数
- 当处理新元素x时:
- 使用二分查找找到x应该插入的位置
- 检查插入后x的出现次数是否超过k
- 如果不超过,则正常插入;如果超过,则需要特殊处理
步骤7:代码框架
def lengthOfLISWithLimit(nums, k):
if not nums:
return 0
n = len(nums)
dp = [1] * n
# freq[i]是一个字典,记录以i结尾的子序列中各个数值的出现次数
freq = [{} for _ in range(n)]
for i in range(n):
freq[i][nums[i]] = 1 # 当前元素至少出现1次
max_len = 1
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
# 检查nums[i]在以j结尾的子序列中的出现次数
current_count = freq[j].get(nums[i], 0)
if current_count < k:
if dp[j] + 1 > dp[i]:
dp[i] = dp[j] + 1
# 更新频率信息
freq[i] = freq[j].copy()
freq[i][nums[i]] = freq[i].get(nums[i], 0) + 1
max_len = max(max_len, dp[i])
return max_len
步骤8:复杂度分析
- 时间复杂度:O(n²)
- 空间复杂度:O(n²)(因为每个位置需要存储一个频率字典)
这个解法虽然不如标准LIS的O(n log n)解法高效,但能正确处理出现次数限制,并且思路清晰易懂。