最长湍流子数组问题(进阶版)
我将为您讲解一个进阶版本的最长湍流子数组问题。这个问题在基础版本上增加了额外的约束条件,使得解题思路更加复杂和有趣。
题目描述
给定一个整数数组 arr,当数组的子数组满足以下条件时,我们称该子数组为湍流子数组:
- 对于每个索引
i(子数组的起始索引 < i < 子数组的结束索引),比较符号在相邻元素之间交替变化:- 如果
arr[i-1] < arr[i],那么arr[i] > arr[i+1] - 如果
arr[i-1] > arr[i],那么arr[i] < arr[i+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 - 如果
arr[i] == arr[i-1]:up[i] = down[i] = 1
进阶版本解法
对于进阶版本,我们需要考虑修改一个元素的情况。我们可以使用三维动态规划:
状态定义
定义 dp[i][state][modified],其中:
i:当前考虑的位置state:当前的状态(0表示上升,1表示下降)modified:是否已经使用过修改机会(0表示未使用,1表示已使用)
状态转移方程
对于每个位置 i,考虑所有可能的情况:
情况1:没有使用修改机会
- 如果
arr[i] > arr[i-1]且前一个状态是下降:
dp[i][0][0] = dp[i-1][1][0] + 1 - 如果
arr[i] < arr[i-1]且前一个状态是上升:
dp[i][1][0] = dp[i-1][0][0] + 1 - 如果不满足湍流条件,重新开始计数
情况2:使用修改机会
这里的关键是我们可以修改 arr[i] 来满足湍流条件:
-
如果前一个状态是下降,我们可以修改
arr[i]使其大于arr[i-1]:
dp[i][0][1] = max(dp[i][0][1], dp[i-1][1][0] + 1) -
如果前一个状态是上升,我们可以修改
arr[i]使其小于arr[i-1]:
dp[i][1][1] = max(dp[i][1][1], dp[i-1][0][0] + 1) -
我们也可以选择不在当前位置使用修改机会,而是继承之前已经使用过修改机会的状态
初始化
对于第一个元素:
dp[0][0][0] = 1(上升状态)dp[0][1][0] = 1(下降状态)dp[0][0][1] = 1dp[0][1][1] = 1
算法步骤
- 初始化DP数组
- 遍历数组中的每个元素
- 对于每个元素,考虑所有可能的状态转移
- 记录过程中的最大值
- 返回最大值作为结果
代码实现
def maxTurbulenceSize(arr):
if not arr:
return 0
n = len(arr)
if n == 1:
return 1
# dp[i][state][modified]
# state: 0-up, 1-down
# modified: 0-no, 1-yes
dp = [[[0] * 2 for _ in range(2)] for _ in range(n)]
# 初始化
dp[0][0][0] = 1
dp[0][1][0] = 1
dp[0][0][1] = 1
dp[0][1][1] = 1
max_len = 1
for i in range(1, n):
# 情况1:不使用修改机会
if arr[i] > arr[i-1]:
# 当前上升,前一个应该是下降
dp[i][0][0] = dp[i-1][1][0] + 1
dp[i][1][0] = 1
elif arr[i] < arr[i-1]:
# 当前下降,前一个应该是上升
dp[i][1][0] = dp[i-1][0][0] + 1
dp[i][0][0] = 1
else:
# 相等,重新开始
dp[i][0][0] = 1
dp[i][1][0] = 1
# 情况2:使用修改机会
# 选择1:在当前位置使用修改机会
# 修改arr[i]使其满足湍流条件
if i >= 1:
# 修改arr[i]使其大于arr[i-1](当前状态为上升)
dp[i][0][1] = max(dp[i][0][1], dp[i-1][1][0] + 1)
# 修改arr[i]使其小于arr[i-1](当前状态为下降)
dp[i][1][1] = max(dp[i][1][1], dp[i-1][0][0] + 1)
# 选择2:不在当前位置使用修改机会,继承之前的状态
if arr[i] > arr[i-1]:
dp[i][0][1] = max(dp[i][0][1], dp[i-1][1][1] + 1)
elif arr[i] < arr[i-1]:
dp[i][1][1] = max(dp[i][1][1], dp[i-1][0][1] + 1)
else:
# 如果相等,我们可以选择修改其中一个来继续序列
dp[i][0][1] = max(dp[i][0][1], dp[i-1][1][1] + 1)
dp[i][1][1] = max(dp[i][1][1], dp[i-1][0][1] + 1)
# 更新最大值
max_len = max(max_len, dp[i][0][0], dp[i][1][0], dp[i][0][1], dp[i][1][1])
return max_len
复杂度分析
- 时间复杂度:O(n),其中n是数组的长度。我们只需要遍历数组一次。
- 空间复杂度:O(n),用于存储DP数组。可以通过滚动数组优化到O(1)。
示例演示
考虑数组 [9,4,2,10,7,8,8,1,9]:
基础版本(不允许修改):
最长的湍流子数组是 [4,2,10,7,8,1,9],长度为7。
进阶版本(允许修改一次):
我们可以修改第二个8(索引6)为其他值,得到更长的湍流子数组 [2,10,7,8,1,9] 或其他更优序列。
这个问题的关键在于理解修改机会的使用时机,以及如何通过动态规划来跟踪不同的状态。通过这种方法,我们能够有效地解决这个进阶版本的湍流子数组问题。