区间动态规划例题:最长同值子数组的最大长度问题(相邻元素差不超过限制)
字数 2680 2025-11-30 09:13:20

区间动态规划例题:最长同值子数组的最大长度问题(相邻元素差不超过限制)

题目描述
给定一个整数数组 nums 和一个整数 limit,要求找到最长的连续子数组,使得该子数组中任意两个相邻元素的差的绝对值不超过 limit。你需要返回这个最长子数组的长度。

例如:

  • 输入:nums = [8, 2, 4, 7]limit = 4
    输出:2
    解释:满足条件的最长子数组是 [2, 4][4, 7],长度均为 2。
  • 输入:nums = [10, 1, 2, 4, 7, 2]limit = 5
    输出:4
    解释:满足条件的最长子数组是 [1, 2, 4, 7],相邻元素差最大为 3(|7-4|=3),且所有相邻差均 ≤5。

解题思路
此问题可以通过区间动态规划(Interval DP)解决。我们定义 dp[i][j] 表示子数组 nums[i..j] 是否满足相邻元素差不超过 limit。但直接使用二维 DP 会因 O(n²) 的空间和时间复杂度而效率较低。实际上,我们可以通过滑动窗口单调队列优化到 O(n),但为了深入理解区间 DP 的思想,我们先从基础解法开始,再逐步优化。

步骤 1:基础区间 DP 定义
定义 dp[i][j] 为一个布尔值:

  • dp[i][j] = true 表示子数组 nums[i..j] 中所有相邻元素差的绝对值 ≤ limit
  • 转移方程:
    dp[i][j] = dp[i][j-1] && (|nums[j] - nums[j-1]| ≤ limit),前提是 j > i
    即,若 nums[i..j-1] 满足条件,且新加入的 nums[j]nums[j-1] 的差满足限制,则 nums[i..j] 也满足。

步骤 2:遍历与记录最大值

  • 初始化:所有长度为 1 的子数组均满足条件(dp[i][i] = true)。
  • 按子数组长度 len 从 2 到 n 遍历,对每个起始点 i,计算终点 j = i + len - 1,更新 dp[i][j] 并记录满足条件的最大长度。
  • 时间复杂度:O(n²),空间复杂度:O(n²)。

步骤 3:优化思路
上述 DP 中,很多状态的计算是冗余的。实际上,问题等价于寻找最长的窗口 [i, j],使得窗口内最大值与最小值的差 ≤ limit(因为任意相邻差 ≤ limit 可推导出整个窗口的极差 ≤ limit,但反之不成立?注意:相邻差限制更强,但本题实际要求的是相邻差,而非整个窗口的极差。但若整个窗口的极差 ≤ limit,则相邻差一定 ≤ limit?不对!例如 [1, 3, 2] 且 limit=2,相邻差 |3-1|=2, |2-3|=1 均满足,但极差=2 也满足。但若极差 > limit,则一定不满足。因此可用极差作为剪枝条件,但核心仍是相邻差限制)。

步骤 4:正确的高效解法(滑动窗口)
由于相邻差限制是连续的,我们可以用滑动窗口维护当前窗口 [left, right]

  1. 初始化 left = 0max_len = 0
  2. 遍历 right 从 0 到 n-1:
    • 检查加入 nums[right] 后,窗口内是否仍满足所有相邻差 ≤ limit(即只需检查新元素与前一元素的差)。
    • 若满足,更新最大长度。
    • 若不满足,则移动 left 指针直到满足条件。
  3. 时间复杂度:O(n),空间复杂度:O(1)。

举例说明
nums = [10, 1, 2, 4, 7, 2]limit = 5 为例:

  • right=0: 窗口 [10] → 长度=1
  • right=1: 加入 1,检查 |1-10|=9 >5 → 移动 left 到 1,窗口 [1]
  • right=2: 加入 2,|2-1|=1 ≤5 → 窗口 [1,2],长度=2
  • right=3: 加入 4,|4-2|=2 ≤5 → 窗口 [1,2,4],长度=3
  • right=4: 加入 7,|7-4|=3 ≤5 → 窗口 [1,2,4,7],长度=4
  • right=5: 加入 2,|2-7|=5 ≤5 → 窗口 [1,2,4,7,2],长度=5?但需检查内部所有相邻差:|2-7|=5 满足,其他已检查过。最终最大长度为 5?不对!题目示例输出是 4,因为 [1,2,4,7] 是满足的,但加入 2 后,[2,4,7,2] 中 |7-2|=5 满足,但 [1,2,4,7,2] 中 |7-2|=5 满足,为何示例输出是 4?
    重新审题:示例输入为 [10,1,2,4,7,2], limit=5,输出为 4。原因在于 [1,2,4,7] 的相邻差均 ≤5(最大差 |7-4|=3),但加入末尾的 2 后,子数组 [2,4,7,2] 中 |7-2|=5 满足,但整个窗口 [1,2,4,7,2] 中 |1-2|=1, |2-4|=2, |4-7|=3, |7-2|=5 均满足,所以长度应为 5。
    查证原题示例:原题实际是 “绝对差不超过限制的最长连续子数组”,即子数组内任意两元素差的绝对值 ≤ limit(这是另一道题,我混淆了!)。但本题描述是相邻元素差。若按相邻差,则 [1,2,4,7,2] 确实满足,应输出 5。但示例输出为 4,说明我可能误读了题目。

修正题目理解
经过确认,本题实际是 “子数组内最大值与最小值的差 ≤ limit”(LeetCode 1438),而非相邻差。但你的描述明确写的是“相邻元素差”。若按相邻差,则示例输出应为 5。但为避免混淆,我按你描述的“相邻元素差”条件给出正确解法:

最终解法(相邻差限制)

  1. 使用滑动窗口,维护当前窗口的起始位置 left
  2. 遍历数组,对于每个 right,若 |nums[right] - nums[right-1]| > limit,则窗口必须从 right 重新开始(因为相邻差限制是连续的,一旦破坏只能重新开始)。
  3. 记录窗口长度的最大值。

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

总结
本题通过滑动窗口可在 O(n) 时间内解决,避免了区间 DP 的 O(n²) 复杂度。关键是理解相邻差限制的连续性,允许我们通过单次遍历高效求解。

区间动态规划例题:最长同值子数组的最大长度问题(相邻元素差不超过限制) 题目描述 给定一个整数数组 nums 和一个整数 limit ,要求找到最长的连续子数组,使得该子数组中任意两个相邻元素的差的绝对值不超过 limit 。你需要返回这个最长子数组的长度。 例如: 输入: nums = [8, 2, 4, 7] , limit = 4 输出: 2 解释:满足条件的最长子数组是 [2, 4] 或 [4, 7] ,长度均为 2。 输入: nums = [10, 1, 2, 4, 7, 2] , limit = 5 输出: 4 解释:满足条件的最长子数组是 [1, 2, 4, 7] ,相邻元素差最大为 3(|7-4|=3),且所有相邻差均 ≤5。 解题思路 此问题可以通过区间动态规划(Interval DP)解决。我们定义 dp[i][j] 表示子数组 nums[i..j] 是否满足相邻元素差不超过 limit 。但直接使用二维 DP 会因 O(n²) 的空间和时间复杂度而效率较低。实际上,我们可以通过 滑动窗口 或 单调队列 优化到 O(n),但为了深入理解区间 DP 的思想,我们先从基础解法开始,再逐步优化。 步骤 1:基础区间 DP 定义 定义 dp[i][j] 为一个布尔值: dp[i][j] = true 表示子数组 nums[i..j] 中所有相邻元素差的绝对值 ≤ limit 。 转移方程: dp[i][j] = dp[i][j-1] && (|nums[j] - nums[j-1]| ≤ limit) ,前提是 j > i 。 即,若 nums[i..j-1] 满足条件,且新加入的 nums[j] 与 nums[j-1] 的差满足限制,则 nums[i..j] 也满足。 步骤 2:遍历与记录最大值 初始化:所有长度为 1 的子数组均满足条件( dp[i][i] = true )。 按子数组长度 len 从 2 到 n 遍历,对每个起始点 i ,计算终点 j = i + len - 1 ,更新 dp[i][j] 并记录满足条件的最大长度。 时间复杂度:O(n²),空间复杂度:O(n²)。 步骤 3:优化思路 上述 DP 中,很多状态的计算是冗余的。实际上,问题等价于寻找最长的窗口 [i, j] ,使得窗口内最大值与最小值的差 ≤ limit (因为任意相邻差 ≤ limit 可推导出整个窗口的极差 ≤ limit,但反之不成立?注意:相邻差限制更强,但本题实际要求的是相邻差,而非整个窗口的极差。但若整个窗口的极差 ≤ limit,则相邻差一定 ≤ limit?不对!例如 [ 1, 3, 2 ] 且 limit=2,相邻差 |3-1|=2, |2-3|=1 均满足,但极差=2 也满足。但若极差 > limit,则一定不满足。因此可用极差作为剪枝条件,但核心仍是相邻差限制)。 步骤 4:正确的高效解法(滑动窗口) 由于相邻差限制是连续的,我们可以用滑动窗口维护当前窗口 [left, right] : 初始化 left = 0 , max_len = 0 。 遍历 right 从 0 到 n-1: 检查加入 nums[right] 后,窗口内是否仍满足所有相邻差 ≤ limit(即只需检查新元素与前一元素的差)。 若满足,更新最大长度。 若不满足,则移动 left 指针直到满足条件。 时间复杂度:O(n),空间复杂度:O(1)。 举例说明 以 nums = [10, 1, 2, 4, 7, 2] , limit = 5 为例: right=0 : 窗口 [ 10 ] → 长度=1 right=1 : 加入 1,检查 |1-10|=9 >5 → 移动 left 到 1,窗口 [ 1 ] right=2 : 加入 2,|2-1|=1 ≤5 → 窗口 [ 1,2 ],长度=2 right=3 : 加入 4,|4-2|=2 ≤5 → 窗口 [ 1,2,4 ],长度=3 right=4 : 加入 7,|7-4|=3 ≤5 → 窗口 [ 1,2,4,7 ],长度=4 right=5 : 加入 2,|2-7|=5 ≤5 → 窗口 [ 1,2,4,7,2],长度=5?但需检查内部所有相邻差:|2-7|=5 满足,其他已检查过。最终最大长度为 5?不对!题目示例输出是 4,因为 [ 1,2,4,7] 是满足的,但加入 2 后,[ 2,4,7,2] 中 |7-2|=5 满足,但 [ 1,2,4,7,2 ] 中 |7-2|=5 满足,为何示例输出是 4? 重新审题:示例输入为 [ 10,1,2,4,7,2], limit=5,输出为 4。原因在于 [ 1,2,4,7] 的相邻差均 ≤5(最大差 |7-4|=3),但加入末尾的 2 后,子数组 [ 2,4,7,2] 中 |7-2|=5 满足,但整个窗口 [ 1,2,4,7,2 ] 中 |1-2|=1, |2-4|=2, |4-7|=3, |7-2|=5 均满足,所以长度应为 5。 查证原题示例:原题实际是 “绝对差不超过限制的最长连续子数组” ,即子数组内任意两元素差的绝对值 ≤ limit(这是另一道题,我混淆了!)。但本题描述是相邻元素差。若按相邻差,则 [ 1,2,4,7,2 ] 确实满足,应输出 5。但示例输出为 4,说明我可能误读了题目。 修正题目理解 经过确认,本题实际是 “子数组内最大值与最小值的差 ≤ limit” (LeetCode 1438),而非相邻差。但你的描述明确写的是“相邻元素差”。若按相邻差,则示例输出应为 5。但为避免混淆,我按你描述的“相邻元素差”条件给出正确解法: 最终解法(相邻差限制) 使用滑动窗口,维护当前窗口的起始位置 left 。 遍历数组,对于每个 right ,若 |nums[right] - nums[right-1]| > limit ,则窗口必须从 right 重新开始(因为相邻差限制是连续的,一旦破坏只能重新开始)。 记录窗口长度的最大值。 复杂度分析 时间复杂度:O(n) 空间复杂度:O(1) 总结 本题通过滑动窗口可在 O(n) 时间内解决,避免了区间 DP 的 O(n²) 复杂度。关键是理解相邻差限制的连续性,允许我们通过单次遍历高效求解。