题目;
我们定义 arr
是 山形数组 当且仅当它满足:
arr.length >= 3
- 存在某个下标
i
(从 0 开始) 满足0 < i < arr.length - 1
且:arr[0] < arr[1] < ... < arr[i - 1] < arr[i]
arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
给你整数数组 nums
,请你返回将 nums
变成 山形状数组 的 最少 删除次数。
示例 1:
输入:nums = [1,3,1] 输出:0 解释:数组本身就是山形数组,所以我们不需要删除任何元素。
示例 2:
输入:nums = [2,1,1,5,6,2,3,1] 输出:3 解释:一种方法是将下标为 0,1 和 5 的元素删除,剩余元素为 [1,5,6,3,1] ,是山形数组。
提示:
3 <= nums.length <= 1000
1 <= nums[i] <= 109
- 题目保证
nums
删除一些元素后一定能得到山形数组。
解题:
-
解决这个问题的关键在于理解如何利用动态规划(DP)来找到任意一个元素作为山顶时的最长山形子序列。这一过程可以分解为几个步骤:
-
定义状态:
- 我们维护两个DP数组来记录状态,即
lis[i]
和lds[i]
。lis[i]
代表以nums[i]
结尾的最长上升子序列的长度(Longest Increasing Subsequence, LIS)。lds[i]
代表从nums[i]
开始的最长下降子序列的长度(Longest Decreasing Subsequence, LDS)。
- 我们维护两个DP数组来记录状态,即
-
状态转移:
- 对于
lis[i]
,我们通过比较nums[i]
与其之前的元素nums[j]
(j < i
)来更新lis[i]
的值。 - 对于
lds[i]
,我们通过比较nums[i]
与其之后的元素nums[j]
(j > i
)来更新lds[i]
的值。
- 对于
-
合并上升和下降序列:
- 一旦我们有了每个元素为山顶时的上升和下降序列长度,我们就可以找到以该元素为山顶时的最长山形序列的长度,即
lis[i] + lds[i] - 1
(因为山顶元素被计算了两次,需减去1)。
- 一旦我们有了每个元素为山顶时的上升和下降序列长度,我们就可以找到以该元素为山顶时的最长山形序列的长度,即
-
找到最长的山形子序列:
- 遍历所有元素,利用上述方法计算以每个元素为山顶的最长山形序列长度,取最大值
maxLength
。
- 遍历所有元素,利用上述方法计算以每个元素为山顶的最长山形序列长度,取最大值
-
计算最小删除次数:
- 最后,用数组总长减去
maxLength
,即得到了得到山形数组的最少删除次数。
- 最后,用数组总长减去
计算LIS:
for (int i = 0; i < n; i++) {lis[i] = 1;for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {lis[i] = Math.max(lis[i], lis[j] + 1);}}
}
计算LDS:
for (int i = n - 1; i >= 0; i--) {lds[i] = 1;for (int j = n - 1; j > i; j--) {if (nums[i] > nums[j]) {lds[i] = Math.max(lds[i], lds[j] + 1);}}
}
找出最长的山形子序列并计算需要删除的元素数量:
int maxLength = 0;
for (int i = 0; i < n; i++) {if (lis[i] > 1 && lds[i] > 1) {maxLength = Math.max(maxLength, lis[i] + lds[i] - 1);}
}
return n - maxLength;
代码:
public class Solution {public int minimumMountainRemovals(int[] nums) {int n = nums.length;int[] lis = new int[n];int[] lds = new int[n];// 计算 LISfor (int i = 0; i < n; i++) {lis[i] = 1;for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {lis[i] = Math.max(lis[i], lis[j] + 1);}}}// 计算 LDSfor (int i = n - 1; i >= 0; i--) {lds[i] = 1;for (int j = n - 1; j > i; j--) {if (nums[i] > nums[j]) {lds[i] = Math.max(lds[i], lds[j] + 1);}}}// 找出最长的山形子序列int maxLength = 0;for (int i = 0; i < n; i++) {if (lis[i] > 1 && lds[i] > 1) {maxLength = Math.max(maxLength, lis[i] + lds[i] - 1);}}// 返回需要删除的元素数量return n - maxLength;}
}
知识点解析:
-
动态规划(Dynamic Programming, DP):利用过去计算的结果来帮助解决后续的子问题,从而避免重复计算。这是解题的核心思想,通过分别计算最长上升子序列(LIS)和最长下降子序列(LDS)来找到最长的山形子序列。
-
数组操作:利用数组存储中间计算结果(如LIS和LDS的长度),以及最终的结果计算。
-
双层循环:用于计算LIS和LDS,内层循环遍历小于当前索引的所有元素,寻找合适的元素更新当前的LIS或LDS值。
-
条件判断:在计算LIS和LDS时,需要通过条件判断来确保只有在当前元素大于遍历到的元素时才进行计算。
-
数学运算:在各个步骤中用到了基本的数学运算,如最大值
Math.max()
的计算。
知识点 | 描述 | 应用 |
---|---|---|
动态规划 | 通过解决子问题的方式来解决主问题,避免重复计算。 | 分别计算最长上升子序列(LIS)和最长下降子序列(LDS)。 |
数组操作 | 使用数组来存储数据和结果。 | 存储LIS和LDS的长度及用于遍历查找的过程中的中间结果。 |
双层循环 | 使用嵌套循环遍历数组中的元素。 | 在计算LIS和LDS时,内层循环遍历并更新当前索引的LIS或LDS值。 |
条件判断 | 根据特定条件来执行不同的逻辑。 | 确保在适当的条件下(如要求严格递增/递减)更新LIS和LDS值。 |
数学运算 | 使用基本的数学运算和函数。 | 使用Math.max() 来找到最大的LIS、LDS值,以及计 |
2024.5.14