Hard
题目描述
你可能还记得,当且仅当满足以下条件时,数组 arr 是一个山形数组:
- arr.length >= 3
- 存在某个下标 i(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 的元素,使数组变为 nums = [1,5,6,3,1]。
约束条件:
- 3 <= nums.length <= 1000
- 1 <= nums[i] <= 10^9
- 题目保证可以从 nums 得到一个山形数组。
提示:
- 思考相反的方向,不是最少删除元素,而是最长的山形子序列
- 考虑最长递增子序列(LIS),这与此问题很相似
解题思路
解题思路
这道题要求找到最少删除次数使数组变成山形数组。我们可以换个角度思考:找到最长的山形子序列,删除次数就是总长度减去最长山形子序列的长度。
山形数组的特点是存在一个峰值,左边严格递增,右边严格递减。对于每个可能的峰值位置 i,我们需要:
- 计算以 i 结尾的最长递增子序列长度(LIS)
- 计算以 i 开始的最长递减子序列长度(LDS)
具体步骤:
- 计算 LIS:对每个位置 i,计算 nums[0…i] 中以 nums[i] 结尾的最长递增子序列长度
- 计算 LDS:对每个位置 i,计算 nums[i…n-1] 中以 nums[i] 开始的最长递减子序列长度
- 枚举峰值:对每个可能的峰值位置 i(要求左右两边都至少有一个元素),计算山形长度 = LIS[i] + LDS[i] - 1
- 找最优解:最长山形子序列长度为所有可能峰值的最大值,最少删除次数 = n - 最长山形长度
可以使用动态规划(O(n²))或二分查找优化(O(n log n))来计算 LIS 和 LDS。这里采用 O(n²) 的 DP 方法,思路更清晰。
山形数组要求峰值两侧都必须有元素,所以 LIS[i] >= 2 且 LDS[i] >= 2 的位置才能作为有效峰值。
代码实现
class Solution {
public:
int minimumMountainRemovals(vector<int>& nums) {
int n = nums.size();
// 计算以每个位置结尾的最长递增子序列长度
vector<int> lis(n, 1);
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
lis[i] = max(lis[i], lis[j] + 1);
}
}
}
// 计算以每个位置开始的最长递减子序列长度
vector<int> lds(n, 1);
for (int i = n - 2; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (nums[i] > nums[j]) {
lds[i] = max(lds[i], lds[j] + 1);
}
}
}
// 找最长的山形子序列
int maxMountain = 0;
for (int i = 1; i < n - 1; i++) {
// 必须左右两边都有元素才能构成山形
if (lis[i] >= 2 && lds[i] >= 2) {
maxMountain = max(maxMountain, lis[i] + lds[i] - 1);
}
}
return n - maxMountain;
}
};
class Solution:
def minimumMountainRemovals(self, nums: List[int]) -> int:
n = len(nums)
# 计算以每个位置结尾的最长递增子序列长度
lis = [1] * n
for i in range(1, n):
for j in range(i):
if nums[j] < nums[i]:
lis[i] = max(lis[i], lis[j] + 1)
# 计算以每个位置开始的最长递减子序列长度
lds = [1] * n
for i in range(n - 2, -1, -1):
for j in range(i + 1, n):
if nums[i] > nums[j]:
lds[i] = max(lds[i], lds[j] + 1)
# 找最长的山形子序列
max_mountain = 0
for i in range(1, n - 1):
# 必须左右两边都有元素才能构成山形
if lis[i] >= 2 and lds[i] >= 2:
max_mountain = max(max_mountain, lis[i] + lds[i] - 1)
return n - max_mountain
public class Solution {
public int MinimumMountainRemovals(int[] nums) {
int n = nums.Length;
// 计算以每个位置结尾的最长递增子序列长度
int[] lis = new int[n];
Array.Fill(lis, 1);
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
lis[i] = Math.Max(lis[i], lis[j] + 1);
}
}
}
// 计算以每个位置开始的最长递减子序列长度
int[] lds = new int[n];
Array.Fill(lds, 1);
for (int i = n - 2; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (nums[i] > nums[j]) {
lds[i] = Math.Max(lds[i], lds[j] + 1);
}
}
}
// 找最长的山形子序列
int maxMountain = 0;
for (int i = 1; i < n - 1; i++) {
// 必须左右两边都有元素才能构成山形
if (lis[i] >= 2 && lds[i] >= 2) {
maxMountain = Math.Max(maxMountain, lis[i] + lds[i] - 1);
}
}
return n - maxMountain;
}
}
/**
* @param {number[]} nums
* @return {number}
*/
var minimumMountainRemovals = function(nums) {
const n = nums.length;
// 计算以每个位置结尾的最长递增子序列长度
const lis = new Array(n).fill(1);
for (let i = 1; i < n; i++) {
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
lis[i] = Math.max(lis[i], lis[j] + 1);
}
}
}
// 计算以每个位置开始的最长递减子序列长度
const lds = new Array(n).fill(1);
for (let i = n - 2; i >= 0; i--) {
for (let j = i + 1; j < n; j++) {
if (nums[i] > nums[j]) {
lds[i] = Math.max(lds[i], lds[j] + 1);
}
}
}
// 找最长的山形子序列
let maxMountain = 0;
for (let i = 1; i < n - 1; i++) {
// 必须左右两边都有元素才能构成山形
if (lis[i] >= 2 && lds[i] >= 2) {
maxMountain = Math.max(maxMountain, lis[i] + lds[i] - 1);
}
}
return n - maxMountain;
};
复杂度分析
| 算法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 动态规划 | O(n²) | O(n) |
说明:
- 时间复杂度:O(n²),需要两次嵌套循环分别计算 LIS 和 LDS
- 空间复杂度:O(n),需要额外的数组存储 LIS 和 LDS 结果
- 可以优化到 O(n log n) 使用二分查找计算 LIS 和 LDS,但代码复杂度会增加
相关题目
. Longest Increasing Subsequence (Medium)
. Longest Mountain in Array (Medium)
. Peak Index in a Mountain Array (Medium)
. Valid Mountain Array (Easy)
. Find in Mountain Array (Hard)
. Beautiful Towers II (Medium)
. Beautiful Towers I (Medium)