线性动态规划:最长湍流子数组的变种——最多允许翻转一个子数组的最长湍流子数组
字数 2616 2025-10-29 11:32:03

线性动态规划:最长湍流子数组的变种——最多允许翻转一个子数组的最长湍流子数组

题目描述
给定一个整数数组 arr,如果比较符号在子数组的每个相邻元素对之间翻转(即 >< 交替出现),则该子数组称为湍流子数组。例如,[4, 2, 10, 7] 是湍流的,因为 4 > 22 < 1010 > 7。现在允许你最多翻转数组中的一个连续子数组(翻转指反转子数组的顺序),然后找到翻转后可能的最长湍流子数组的长度。

示例
输入:arr = [9, 4, 2, 10, 7, 8, 8, 1, 9]
输出:6
解释:翻转子数组 [8, 8](实际上翻转后仍是 [8, 8],但允许操作),可以得到湍流子数组 [4, 2, 10, 7, 8, 1][2, 10, 7, 8, 1, 9],长度为 6。


解题过程

步骤 1:理解基础湍流子数组问题
首先,回顾经典湍流子数组问题(无需翻转):定义 up[i] 为以 i 结尾且最后一段是上升(arr[i] > arr[i-1])的最长湍流子数组长度;down[i] 为以 i 结尾且最后一段是下降(arr[i] < arr[i-1])的长度。转移方程为:

  • arr[i] > arr[i-1]up[i] = down[i-1] + 1down[i] = 1
  • arr[i] < arr[i-1]down[i] = up[i-1] + 1up[i] = 1
  • 若相等:up[i] = down[i] = 1
    最终答案是所有 max(up[i], down[i]) 的最大值。

步骤 2:分析翻转操作的影响
翻转一个连续子数组相当于将原数组分成三部分:[左段, 中段, 右段],翻转中段后,中段内部符号顺序反转,同时中段与左段的连接处、中段与右段的连接处的符号关系可能改变。目标是最大化翻转后的全局湍流子数组长度。

关键观察

  1. 翻转操作最多只能修复一个“断点”(即不满足湍流条件的位置)。
  2. 最优解通常是通过翻转一个短子数组(甚至长度为 0 或 1)来连接左右两个湍流段。

步骤 3:定义状态和预处理
我们预处理以下数组:

  • left_up[i]:从 i 向左的最长湍流子数组长度,且以 i 结尾是上升(即 arr[i] > arr[i-1])。
  • left_down[i]:从 i 向左的最长湍流子数组长度,且以 i 结尾是下降。
  • right_up[i]:从 i 向右的最长湍流子数组长度,且以 i 开头是上升。
  • right_down[i]:从 i 向右的最长湍流子数组长度,且以 i 开头是下降。

这些数组可以通过左右各扫描一次得到,类似经典问题但方向不同。

步骤 4:枚举翻转中段的左右端点
直接枚举所有子数组翻转会超时(O(n²))。优化:考虑翻转中段长度为 k,则中段翻转后,其内部符号反转,但湍流性质保留(因为符号交替性在反转后依然保持)。因此,翻转中段本身不影响内部湍流长度,关键在于连接左右段。

更优策略:枚举连接点。对于每个位置 i,检查以下情况:

  1. 不翻转:直接取 max(left_up[i], left_down[i])
  2. 翻转一个子数组来连接 i 左右的湍流段:
    • 情况 A:翻转 [i, i+1](即交换 arr[i]arr[i+1]),检查是否能连接左右段。
    • 情况 B:翻转 [i-1, i],类似。
    • 情况 C:翻转更长的子数组,但实际最优解通常只需翻转 2 个元素或更短。

实际上,只需考虑翻转长度为 0(不翻转)、1(无意义)、2、3 的情况,因为更长翻转可以分解为短翻转的组合。

步骤 5:具体枚举情况
我们枚举每个索引 i 作为潜在连接点,计算以下可能:

  • 不翻转:答案至少为 max(left_up[i], left_down[i])
  • 翻转两个元素(如 [i, i+1]):
    翻转后,arr[i]arr[i+1] 交换,检查 arr[i-1]arr[i+1] 的关系、arr[i]arr[i+2] 的关系是否能连接左右湍流段。
    例如,若 left_down[i-1] 表示以 i-1 结尾是下降,则要求 arr[i-1] > arr[i+1] 才能连接;同时右段要求 arr[i] < arr[i+2] 才能接上 right_up[i+2]
    连接后长度 = left_down[i-1] + 2 + right_up[i+2]
  • 翻转三个元素(如 [i-1, i+1]):类似,但中段长度为 3,翻转后中段内部符号反转,连接处检查 arr[i-2]arr[i+1]arr[i-1]arr[i+2] 的关系。

步骤 6:算法实现概要

  1. 预处理 left_up, left_down, right_up, right_down
  2. 初始化答案 ans 为不翻转时的最大湍流子数组长度。
  3. 对于每个 i 从 1 到 n-2:
    • 检查翻转 [i, i+1]
      • arr[i-1] > arr[i+1] 且与 left_down[i-1] 兼容,则可能连接左段。
      • arr[i] < arr[i+2] 且与 right_up[i+2] 兼容,则连接右段。
      • 更新 ans
    • 检查翻转 [i-1, i]:类似。
    • 检查翻转 [i-1, i+1]:类似但考虑中段长度 3。
  4. 注意边界处理(索引不越界)。

步骤 7:复杂度分析
预处理 O(n),枚举 O(n),总复杂度 O(n),空间 O(n)。

示例验证
arr = [9,4,2,10,7,8,8,1,9]

  • 不翻转时最长湍流子数组为 5(如 [4,2,10,7,8])。
  • 翻转 [8,8](索引 6~7)后,可连接 [2,10,7][1,9],得到长度 6。
    算法会枚举到连接点 i=7 左右,通过翻转短子数组得到更长结果。

通过以上步骤,即可求出最多翻转一个子数组后的最长湍流子数组长度。

线性动态规划:最长湍流子数组的变种——最多允许翻转一个子数组的最长湍流子数组 题目描述 给定一个整数数组 arr ,如果比较符号在子数组的每个相邻元素对之间翻转(即 > 和 < 交替出现),则该子数组称为湍流子数组。例如, [4, 2, 10, 7] 是湍流的,因为 4 > 2 、 2 < 10 、 10 > 7 。现在允许你最多翻转数组中的一个连续子数组(翻转指反转子数组的顺序),然后找到翻转后可能的最长湍流子数组的长度。 示例 输入: arr = [9, 4, 2, 10, 7, 8, 8, 1, 9] 输出: 6 解释:翻转子数组 [8, 8] (实际上翻转后仍是 [8, 8] ,但允许操作),可以得到湍流子数组 [4, 2, 10, 7, 8, 1] 或 [2, 10, 7, 8, 1, 9] ,长度为 6。 解题过程 步骤 1:理解基础湍流子数组问题 首先,回顾经典湍流子数组问题(无需翻转):定义 up[i] 为以 i 结尾且最后一段是上升( arr[i] > arr[i-1] )的最长湍流子数组长度; down[i] 为以 i 结尾且最后一段是下降( arr[i] < arr[i-1] )的长度。转移方程为: 若 arr[i] > arr[i-1] : up[i] = down[i-1] + 1 , down[i] = 1 若 arr[i] < arr[i-1] : down[i] = up[i-1] + 1 , up[i] = 1 若相等: up[i] = down[i] = 1 最终答案是所有 max(up[i], down[i]) 的最大值。 步骤 2:分析翻转操作的影响 翻转一个连续子数组相当于将原数组分成三部分: [左段, 中段, 右段] ,翻转中段后,中段内部符号顺序反转,同时中段与左段的连接处、中段与右段的连接处的符号关系可能改变。目标是最大化翻转后的全局湍流子数组长度。 关键观察 : 翻转操作最多只能修复一个“断点”(即不满足湍流条件的位置)。 最优解通常是通过翻转一个短子数组(甚至长度为 0 或 1)来连接左右两个湍流段。 步骤 3:定义状态和预处理 我们预处理以下数组: left_up[i] :从 i 向左的最长湍流子数组长度,且以 i 结尾是上升(即 arr[i] > arr[i-1] )。 left_down[i] :从 i 向左的最长湍流子数组长度,且以 i 结尾是下降。 right_up[i] :从 i 向右的最长湍流子数组长度,且以 i 开头是上升。 right_down[i] :从 i 向右的最长湍流子数组长度,且以 i 开头是下降。 这些数组可以通过左右各扫描一次得到,类似经典问题但方向不同。 步骤 4:枚举翻转中段的左右端点 直接枚举所有子数组翻转会超时(O(n²))。优化:考虑翻转中段长度为 k ,则中段翻转后,其内部符号反转,但湍流性质保留(因为符号交替性在反转后依然保持)。因此,翻转中段本身不影响内部湍流长度,关键在于连接左右段。 更优策略 :枚举连接点。对于每个位置 i ,检查以下情况: 不翻转:直接取 max(left_up[i], left_down[i]) 。 翻转一个子数组来连接 i 左右的湍流段: 情况 A:翻转 [i, i+1] (即交换 arr[i] 和 arr[i+1] ),检查是否能连接左右段。 情况 B:翻转 [i-1, i] ,类似。 情况 C:翻转更长的子数组,但实际最优解通常只需翻转 2 个元素或更短。 实际上,只需考虑翻转长度为 0(不翻转)、1(无意义)、2、3 的情况,因为更长翻转可以分解为短翻转的组合。 步骤 5:具体枚举情况 我们枚举每个索引 i 作为潜在连接点,计算以下可能: 不翻转 :答案至少为 max(left_up[i], left_down[i]) 。 翻转两个元素 (如 [i, i+1] ): 翻转后, arr[i] 和 arr[i+1] 交换,检查 arr[i-1] 与 arr[i+1] 的关系、 arr[i] 与 arr[i+2] 的关系是否能连接左右湍流段。 例如,若 left_down[i-1] 表示以 i-1 结尾是下降,则要求 arr[i-1] > arr[i+1] 才能连接;同时右段要求 arr[i] < arr[i+2] 才能接上 right_up[i+2] 。 连接后长度 = left_down[i-1] + 2 + right_up[i+2] 。 翻转三个元素 (如 [i-1, i+1] ):类似,但中段长度为 3,翻转后中段内部符号反转,连接处检查 arr[i-2] 与 arr[i+1] 、 arr[i-1] 与 arr[i+2] 的关系。 步骤 6:算法实现概要 预处理 left_up , left_down , right_up , right_down 。 初始化答案 ans 为不翻转时的最大湍流子数组长度。 对于每个 i 从 1 到 n-2: 检查翻转 [i, i+1] : 若 arr[i-1] > arr[i+1] 且与 left_down[i-1] 兼容,则可能连接左段。 若 arr[i] < arr[i+2] 且与 right_up[i+2] 兼容,则连接右段。 更新 ans 。 检查翻转 [i-1, i] :类似。 检查翻转 [i-1, i+1] :类似但考虑中段长度 3。 注意边界处理(索引不越界)。 步骤 7:复杂度分析 预处理 O(n),枚举 O(n),总复杂度 O(n),空间 O(n)。 示例验证 对 arr = [9,4,2,10,7,8,8,1,9] : 不翻转时最长湍流子数组为 5(如 [4,2,10,7,8] )。 翻转 [8,8] (索引 6~7)后,可连接 [2,10,7] 和 [1,9] ,得到长度 6。 算法会枚举到连接点 i=7 左右,通过翻转短子数组得到更长结果。 通过以上步骤,即可求出最多翻转一个子数组后的最长湍流子数组长度。