问题来源:leetcode 978。
最长湍流子数组
当 A
的子数组 A[i], A[i+1], ..., A[j]
满足下列条件时,我们称其为湍流子数组:
- 若
i <= k < j
,当k
为奇数时,A[k] > A[k+1]
,且当k
为偶数时,A[k] < A[k+1]
; - 或 若
i <= k < j
,当k
为偶数时,A[k] > A[k+1]
,且当k
为奇数时,A[k] < A[k+1]
。
也就是说,如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是湍流子数组。
返回 A
的最大湍流子数组的长度。
示例 1:
输入:[9,4,2,10,7,8,8,1,9]
输出:5
解释:(A[1] > A[2] < A[3] > A[4] < A[5])
示例 2:
输入:[4,8,12,16]
输出:2
示例 3:
输入:[100]
输出:1
提示:
1 <= A.length <= 40000
0 <= A[i] <= 10^9
动态规划
优化子结构:设以 a r r [ i ] arr[i] arr[i] 为结尾的最长湍流子数组的长度为 n n n,证明该问题的最优解包含其子问题的最优解,或者说该问题的最优解可以由其子问题的最优解构造得到:
- 如果 a r r [ i ] = = a r r [ i − 1 ] arr[i] == arr[i-1] arr[i]==arr[i−1],那么以 a r r [ i ] arr[i] arr[i] 为结尾的最长湍流子数组的长度为 1 1 1,不需要子问题的解。
- 如果 a r r [ i ] > a r r [ i − 1 ] arr[i] > arr[i-1] arr[i]>arr[i−1] 且 a r r [ i − 1 ] < a r r [ i − 2 ] arr[i-1] < arr[i-2] arr[i−1]<arr[i−2],那么 n − 1 n-1 n−1 是子问题(以 a r r [ i − 1 ] arr[i-1] arr[i−1] 为结尾的数组)的最长湍流子数组的长度,也就是说只需要求解该子问题。可以通过反证法证明:假设子问题的最长湍流子数组的长度大于 n − 1 n-1 n−1,那么又因为 a r r [ i ] > a r r [ i − 1 ] arr[i] > arr[i-1] arr[i]>arr[i−1] 且 a r r [ i − 1 ] < a r r [ i − 2 ] arr[i-1] < arr[i-2] arr[i−1]<arr[i−2],所以 a r r [ i ] arr[i] arr[i] 可以接在该湍流子数组的后面,所以以 a r r [ i ] arr[i] arr[i] 为结尾的最长湍流子数组的长度就大于 n n n,与假设矛盾。
- 如果 a r r [ i ] < a r r [ i − 1 ] arr[i] < arr[i-1] arr[i]<arr[i−1] 且 a r r [ i − 1 ] > a r r [ i − 2 ] arr[i-1] > arr[i-2] arr[i−1]>arr[i−2],那么 n − 1 n-1 n−1 是子问题(以 a r r [ i − 1 ] arr[i-1] arr[i−1] 为结尾的数组)的最长湍流子数组的长度,证明过程与前面相同。
- 对于其他情况( a r r [ i ] > a r r [ i − 1 ] > a r r [ i − 2 ] arr[i] > arr[i-1] > arr[i-2] arr[i]>arr[i−1]>arr[i−2] 或 a r r [ i ] < a r r [ i − 1 ] < a r r [ i − 2 ] arr[i] < arr[i-1] <arr[i-2] arr[i]<arr[i−1]<arr[i−2]),那么无论以 a r r [ i − 1 ] arr[i-1] arr[i−1] 为结尾的最长湍流子数组的长度为多少,以 a r r [ i ] arr[i] arr[i] 结尾的最长湍流子数组的长度为 2。
- 还有 a r r [ i − 2 ] arr[i-2] arr[i−2] 不存在的情况,只需要看 a r r [ i ] arr[i] arr[i] 与 a r r [ i ] arr[i] arr[i] 是否相等,就可以得到原问题的解。
重叠子问题:简单地使用递归算法来求解,很容易发现具有子问题被重复计算。
递归地定义最优解的值:设 d p [ i ] dp[i] dp[i] 表示以 a r r [ i ] arr[i] arr[i] 结尾的最长湍流子数组的长度,对于数组的每个位置 i i i,以该位置元素结尾的最长湍流子数组的长度计算过程如下:
- 首先初始化 d p dp dp 数组的所有位置都为 1 1 1。
- 如果
d
p
[
i
−
1
]
dp[i-1]
dp[i−1] 为 1:
- 如果 a r r [ i ] ≠ a r r [ i − 1 ] arr[i] \neq arr[i-1] arr[i]=arr[i−1] 时, d p [ i ] = 2 dp[i]=2 dp[i]=2;
- 否则保持 1 1 1 不变。
- 否则:
- 如果 a r r [ i ] > a r r [ i − 1 ] arr[i] > arr[i-1] arr[i]>arr[i−1] 且 a r r [ i − 1 ] < a r r [ i − 2 ] arr[i-1] < arr[i-2] arr[i−1]<arr[i−2] 或 a r r [ i ] < a r r [ i − 1 ] arr[i] < arr[i-1] arr[i]<arr[i−1] 且 a r r [ i − 1 ] > a r r [ i − 2 ] arr[i-1] > arr[i-2] arr[i−1]>arr[i−2],那么 d p [ i ] = d p [ i − 1 ] + 1 dp[i] = dp[i-1] + 1 dp[i]=dp[i−1]+1;
- 否则看 a r r [ i − 1 ] arr[i-1] arr[i−1] 与 a r r [ i ] arr[i] arr[i] 是否相等,不相等的话将 d p [ i ] dp[i] dp[i] 的值置为 2 2 2。
自底向上地计算最优解的值:只需要从前向后遍历数组,便可以确保每一个问题计算时,其相关的子问题均已经被计算了出来。
class Solution {
public:
int maxTurbulenceSize(vector<int>& arr) {
int n = arr.size();
if(n == 1) {
return 1;
}
vector<int> dp(n, 1);
for(int i = 1; i<n; i++) {
if(dp[i-1] == 1) {
dp[i] = arr[i-1] == arr[i] ? 1 : 2;
} else if(arr[i] != arr[i-1]) {
if((arr[i] > arr[i-1] && arr[i-1] < arr[i-2]) || (arr[i] < arr[i-1] && arr[i-1] > arr[i-2])) {
dp[i] = dp[i-1] + 1;
} else {
dp[i] = 2;
}
}
}
return *max_element(dp.begin(), dp.end());
}
};
空间优化:注意到每个 d p [ i ] dp[i] dp[i] 只与 d p [ i − 1 ] dp[i-1] dp[i−1] 相关,所以可以通过一个变量来表示 d p dp dp 数组,另外再通过一个变量保存当前最长湍流子数组的长度即可。
class Solution {
public:
int maxTurbulenceSize(vector<int>& arr) {
int n = arr.size();
if(n == 1) {
return 1;
}
int result = 1, temp = 1;
for(int i = 1; i<n; i++) {
if(temp == 1) {
temp = arr[i-1] == arr[i] ? 1 : 2;
} else if(arr[i] != arr[i-1]) {
if((arr[i] > arr[i-1] && arr[i-1] < arr[i-2]) || (arr[i] < arr[i-1] && arr[i-1] > arr[i-2])) {
temp++;
} else {
temp = 2;
}
}
result = temp > result ? temp : result;
}
return result;
}
};