最长递增子序列的变种:带区间限制的最长递增子序列
字数 2429 2025-12-10 01:56:59

最长递增子序列的变种:带区间限制的最长递增子序列

题目描述
给定一个长度为 n 的整数数组 nums,以及一个整数 k。要求找出最长递增子序列(LIS)的长度,满足这个子序列中任意两个相邻元素在原数组中的下标差不超过 k。换句话说,如果我们选取的子序列为 nums[i₁], nums[i₂], ..., nums[iₘ],其中 i₁ < i₂ < ... < iₘ,那么对于所有 j = 1, 2, ..., m‑1,需要满足 iⱼ₊₁ − iⱼ ≤ k。

举例
输入:nums = [10, 9, 2, 5, 3, 7, 101, 18], k = 3
输出:4
解释:最长满足区间限制的递增子序列之一是 [2, 5, 7, 101](下标分别是 2, 3, 5, 6,相邻下标差均 ≤ 3)。


解题过程

1. 理解限制条件
经典的最长递增子序列问题只要求子序列元素单调递增,且在原数组中的顺序保持。本题额外增加了一个下标距离限制 k:当我们从前面的某个位置 i 转移到后面的位置 j 时,必须满足 j − i ≤ k。
这意味着我们在递推时,不能像经典 LIS 那样查看所有前面的位置,而只能查看最多前 k 个位置。

2. 定义状态
设 dp[i] 表示以 nums[i] 结尾的、满足区间限制的最长递增子序列的长度。
我们的最终答案是 max(dp[i]),其中 i 从 0 到 n‑1。

3. 状态转移方程
对于每个 i,我们需要考虑所有满足以下两个条件的 j:

  • 0 ≤ j < i
  • i − j ≤ k
  • nums[j] < nums[i]

如果找到这样的 j,那么我们可以将 nums[i] 接在以 nums[j] 结尾的子序列后面,形成更长的递增子序列。
因此:

dp[i] = max{ dp[j] + 1 | 0 ≤ j < i, i‑j ≤ k, nums[j] < nums[i] }

如果不存在这样的 j,则 dp[i] = 1(只包含自身)。

4. 初始条件
dp[i] 至少为 1(每个元素自身构成一个子序列)。
所以可以初始化 dp 数组全部为 1。

5. 直接动态规划的时间复杂度
如果对每个 i,我们都向前检查最多 k 个 j,那么总时间复杂度为 O(nk)。
在 k 接近 n 时,最坏为 O(n²),与经典 LIS 的 O(n²) 相同。
但通常 k 远小于 n,所以 O(nk) 在多数情况下可接受。

6. 优化思路
如果 k 很大(接近 n),我们可以考虑用线段树树状数组来优化。
具体做法:

  • 我们需要快速查询在区间 [i‑k, i‑1] 内,所有值小于 nums[i] 的位置 j 中,最大的 dp[j] 是多少。
  • 这可以通过将 nums[i] 的值作为下标,建立值域上的线段树,维护该值对应的最大 dp 值。
  • 当处理到 i 时,先查询值域在 [minVal, nums[i]‑1] 的最大 dp 值,然后更新线段树中 nums[i] 对应的值为 dp[i]。
    这样时间复杂度可降为 O(n log M),其中 M 为值域范围(可通过离散化压缩到 n)。

7. 算法步骤(O(nk) 方法)

  1. 初始化 dp 数组,长度 n,全部设为 1。
  2. 从 i = 0 到 n‑1:
    • 令 start = max(0, i‑k)
    • 从 j = start 到 i‑1:
      • 如果 nums[j] < nums[i] 且 dp[j] + 1 > dp[i],则更新 dp[i] = dp[j] + 1
  3. 遍历 dp 数组,返回最大值。

8. 举例演算
nums = [10, 9, 2, 5, 3, 7, 101, 18], k = 3
n = 8, dp 初始全 1。

i=0: 前面无元素,dp[0]=1
i=1: j 从 max(0,1‑3)=0 到 0,nums[0]=10 > 9,不满足递增,dp[1]=1
i=2: j 从 max(0,2‑3)=0 到 1
j=0: 10>2 ✗
j=1: 9>2 ✗
dp[2]=1
i=3: j 从 max(0,3‑3)=0 到 2
j=0: 10>5 ✗
j=1: 9>5 ✗
j=2: 2<5 ✓,dp[2]+1=2,所以 dp[3]=2
i=4: j 从 max(0,4‑3)=1 到 3
j=1: 9>3 ✗
j=2: 2<3 ✓,dp[2]+1=2,dp[4]=2
j=3: 5>3 ✗
i=5: j 从 max(0,5‑3)=2 到 4
j=2: 2<7 ✓,dp[2]+1=2,暂存 2
j=3: 5<7 ✓,dp[3]+1=3,更新 dp[5]=3
j=4: 3<7 ✓,dp[4]+1=3,不增加
i=6: j 从 max(0,6‑3)=3 到 5
j=3: 5<101 ✓,dp[3]+1=3
j=4: 3<101 ✓,dp[4]+1=3
j=5: 7<101 ✓,dp[5]+1=4,更新 dp[6]=4
i=7: j 从 max(0,7‑3)=4 到 6
j=4: 3<18 ✓,dp[4]+1=3
j=5: 7<18 ✓,dp[5]+1=4,更新 dp[7]=4
j=6: 101>18 ✗

最终 dp = [1,1,1,2,2,3,4,4],最大值 4。

9. 边界情况

  • 如果 k ≥ n‑1,则退化为经典 LIS 问题。
  • 如果 k = 0,则子序列长度只能为 1。
  • 数组可能为空,返回 0。

10. 核心要点
本题是 LIS 的一个自然变种,通过限制相邻选取元素的下标差,使得递推时只检查最近 k 个位置,从而在 k 较小时更高效。
如需进一步优化,可用数据结构在值域上查询最大值,达到 O(n log n) 的复杂度。

通过以上步骤,你就可以在满足下标距离限制的条件下,求出最长递增子序列的长度了。

最长递增子序列的变种:带区间限制的最长递增子序列 题目描述 给定一个长度为 n 的整数数组 nums,以及一个整数 k。要求找出最长递增子序列(LIS)的长度,满足这个子序列中任意两个相邻元素在原数组中的下标差不超过 k。换句话说,如果我们选取的子序列为 nums[ i₁], nums[ i₂], ..., nums[ iₘ],其中 i₁ < i₂ < ... < iₘ,那么对于所有 j = 1, 2, ..., m‑1,需要满足 iⱼ₊₁ − iⱼ ≤ k。 举例 输入:nums = [ 10, 9, 2, 5, 3, 7, 101, 18 ], k = 3 输出:4 解释:最长满足区间限制的递增子序列之一是 [ 2, 5, 7, 101 ](下标分别是 2, 3, 5, 6,相邻下标差均 ≤ 3)。 解题过程 1. 理解限制条件 经典的最长递增子序列问题只要求子序列元素单调递增,且在原数组中的顺序保持。本题额外增加了一个 下标距离限制 k :当我们从前面的某个位置 i 转移到后面的位置 j 时,必须满足 j − i ≤ k。 这意味着我们在递推时,不能像经典 LIS 那样查看所有前面的位置,而只能查看最多前 k 个位置。 2. 定义状态 设 dp[ i] 表示以 nums[ i ] 结尾的、满足区间限制的最长递增子序列的长度。 我们的最终答案是 max(dp[ i ]),其中 i 从 0 到 n‑1。 3. 状态转移方程 对于每个 i,我们需要考虑所有满足以下两个条件的 j: 0 ≤ j < i i − j ≤ k nums[ j] < nums[ i ] 如果找到这样的 j,那么我们可以将 nums[ i] 接在以 nums[ j ] 结尾的子序列后面,形成更长的递增子序列。 因此: 如果不存在这样的 j,则 dp[ i ] = 1(只包含自身)。 4. 初始条件 dp[ i ] 至少为 1(每个元素自身构成一个子序列)。 所以可以初始化 dp 数组全部为 1。 5. 直接动态规划的时间复杂度 如果对每个 i,我们都向前检查最多 k 个 j,那么总时间复杂度为 O(nk)。 在 k 接近 n 时,最坏为 O(n²),与经典 LIS 的 O(n²) 相同。 但通常 k 远小于 n,所以 O(nk) 在多数情况下可接受。 6. 优化思路 如果 k 很大(接近 n),我们可以考虑用 线段树 或 树状数组 来优化。 具体做法: 我们需要快速查询在区间 [ i‑k, i‑1] 内,所有值小于 nums[ i] 的位置 j 中,最大的 dp[ j ] 是多少。 这可以通过将 nums[ i ] 的值作为下标,建立值域上的线段树,维护该值对应的最大 dp 值。 当处理到 i 时,先查询值域在 [ minVal, nums[ i]‑1] 的最大 dp 值,然后更新线段树中 nums[ i] 对应的值为 dp[ i ]。 这样时间复杂度可降为 O(n log M),其中 M 为值域范围(可通过离散化压缩到 n)。 7. 算法步骤(O(nk) 方法) 初始化 dp 数组,长度 n,全部设为 1。 从 i = 0 到 n‑1: 令 start = max(0, i‑k) 从 j = start 到 i‑1: 如果 nums[ j] < nums[ i] 且 dp[ j] + 1 > dp[ i],则更新 dp[ i] = dp[ j ] + 1 遍历 dp 数组,返回最大值。 8. 举例演算 nums = [ 10, 9, 2, 5, 3, 7, 101, 18 ], k = 3 n = 8, dp 初始全 1。 i=0: 前面无元素,dp[ 0 ]=1 i=1: j 从 max(0,1‑3)=0 到 0,nums[ 0]=10 > 9,不满足递增,dp[ 1 ]=1 i=2: j 从 max(0,2‑3)=0 到 1 j=0: 10>2 ✗ j=1: 9>2 ✗ dp[ 2 ]=1 i=3: j 从 max(0,3‑3)=0 到 2 j=0: 10>5 ✗ j=1: 9>5 ✗ j=2: 2<5 ✓,dp[ 2]+1=2,所以 dp[ 3 ]=2 i=4: j 从 max(0,4‑3)=1 到 3 j=1: 9>3 ✗ j=2: 2<3 ✓,dp[ 2]+1=2,dp[ 4 ]=2 j=3: 5>3 ✗ i=5: j 从 max(0,5‑3)=2 到 4 j=2: 2<7 ✓,dp[ 2 ]+1=2,暂存 2 j=3: 5<7 ✓,dp[ 3]+1=3,更新 dp[ 5 ]=3 j=4: 3<7 ✓,dp[ 4 ]+1=3,不增加 i=6: j 从 max(0,6‑3)=3 到 5 j=3: 5<101 ✓,dp[ 3 ]+1=3 j=4: 3<101 ✓,dp[ 4 ]+1=3 j=5: 7<101 ✓,dp[ 5]+1=4,更新 dp[ 6 ]=4 i=7: j 从 max(0,7‑3)=4 到 6 j=4: 3<18 ✓,dp[ 4 ]+1=3 j=5: 7<18 ✓,dp[ 5]+1=4,更新 dp[ 7 ]=4 j=6: 101>18 ✗ 最终 dp = [ 1,1,1,2,2,3,4,4 ],最大值 4。 9. 边界情况 如果 k ≥ n‑1,则退化为经典 LIS 问题。 如果 k = 0,则子序列长度只能为 1。 数组可能为空,返回 0。 10. 核心要点 本题是 LIS 的一个自然变种,通过限制相邻选取元素的下标差,使得递推时只检查最近 k 个位置,从而在 k 较小时更高效。 如需进一步优化,可用数据结构在值域上查询最大值,达到 O(n log n) 的复杂度。 通过以上步骤,你就可以在满足下标距离限制的条件下,求出最长递增子序列的长度了。