我们定义 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] ,是山形数组。
二分查找
class Solution {
public:
int minimumMountainRemovals(vector<int>& nums) {
int n = nums.size();
vector<int> lis(n, 1), lds(n, 1);
vector<int> inc;
for (int i = 0; i < n; ++i) {
auto it = lower_bound(inc.begin(), inc.end(), nums[i]);
if (it == inc.end()) {
inc.push_back(nums[i]);
} else {
*it = nums[i];
}
lis[i] = inc.size();
}
vector<int> dec;
// 计算从右到左的最长递减子序列 (LDS) 长度,使用二分查找
for (int i = n - 1; i >= 0; --i) {
auto it = lower_bound(dec.begin(), dec.end(), nums[i]);
if (it == dec.end()) {
dec.push_back(nums[i]);
} else {
*it = nums[i];
}
lds[i] = dec.size();
}
int minRemovals = n;
for (int i = 1; i < n - 1; ++i) {
if (lis[i] > 1 && lds[i] > 1) {
// 山峰的长度是 lis[i] + lds[i] - 1
minRemovals = min(minRemovals, n - (lis[i] + lds[i] - 1));
}
}
return minRemovals;
}
};
时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。
空间复杂度:O(n)。
这道题的思路就是构建一个最长递增子序列LIS和最长递减子序列LDS。
开始,我们定义vector<int> lis(n, 1), lds(n, 1);
来储存以nums[i]结尾的最长递增/递减子序列长度。
我们定义一个迭代器it,指向通过二分查找找到的inc数组中的大于等于nums[i]的元素。
需要注意是
由于 upper_bound 查找的是严格大于当前值的元素,它确保了序列是非严格递增的,即允许相同的元素出现在序列中。例如,如果当前序列为 1, 2, 3,新加入的元素也是 3,upper_bound 会找到序列末尾并在适当位置插入新的 3,从而维持非严格递增的性质。
而lower_bound查找的是大于等于nums[i]的元素,所以nums[i]如果有与inc中相等的元素,会替换而不是推入,这保证了最长递增子序列是严格递增的。
我们还定义了lis和lds来储存不同nums[i]结尾的最长递增。递减子序列的长度。我们遍历每个元素结尾的最长递增子序列和最长递减子序列的长度。·然后通过minRemovals来记录山峰数组的最少删除次数。
最后返回minRemovals。