力扣每日一题;题序:1671
(参照题解中的:灵茶山艾府的解法)
要想删除次数最少,山形数组的子序列长度越长越好。至于最终的最长是多少,这个可以将每一个位置都当做山峰来枚举,计算每个位置的山形子序列的最长长度。
山形子序列是一个严格递增的子序列+严格递减的子序列:
- 定义pre[i]表示子序列最后一个数是nums[i]的最长严格递增子序列的长度
- 定义suf[i]表示子序列第一个数是nums[i]的最长严格递减子序列的长度
这道题与昨日的山状数组有所不同,就是眼球山峰左右两侧必须有数字,所以pre[i]>=2并且suf[i]>=2。因而得到以nums[i]为山顶的最长山状子序列的长度为:pre[i]+suf[i]-1。
计算suf相当于求从右到左遍历nums求最长严格递增子序列,在计算时为了简便采用反转数组来实现。同理,计算pre相当于从左到右遍历nums求最长递增子序列。求最长严格递增子序列的方法有:动态规划和二分查找两种,这里采用二分查找。
最终结果:数组长度-山形子序列的最长长度
时间复杂度:O(nlogn)
空间复杂度:O(n)
public int minimumMountainRemovals(int[] nums) {
int n = nums.length;
int[] suf = new int[n];
List<Integer> g = new ArrayList<>();
for (int i = n - 1; i > 0; i--) {
int x = nums[i];
int j = lowerBound(g, x);
if (j == g.size()) {
g.add(x);
} else {
g.set(j, x);
}
suf[i] = j + 1; // 从 nums[i] 开始的最长严格递减子序列的长度
}
int mx = 0;
g.clear();
for (int i = 0; i < n - 1; i++) {
int x = nums[i];
int j = lowerBound(g, x);
if (j == g.size()) {
g.add(x);
} else {
g.set(j, x);
}
int pre = j + 1; // 在 nums[i] 结束的最长严格递增子序列的长度
if (pre >= 2 && suf[i] >= 2) {
mx = Math.max(mx, pre + suf[i] - 1); // 减去重复的 nums[i]
}
}
return n - mx;
}
private int lowerBound(List<Integer> g, int target) {
int left = -1, right = g.size(); // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// nums[left] < target
// nums[right] >= target
int mid = (left + right) >>> 1;
if (g.get(mid) < target) {
left = mid; // 范围缩小到 (mid, right)
} else {
right = mid; // 范围缩小到 (left, mid)
}
}
return right; // 或者 left+1
}
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈~