题目回顾
给定一个整数数组 nums,每次可以从数组的左端或右端删除一个元素。求最少需要删除多少个元素,才能将数组中的最大值和最小值都删除掉。
核心思路:找到最大值和最小值的位置,然后从三种删除策略中取最小值。
这是一道简单题,但是找出位置的方法可以不同,用来巩固知识刚好。
三种删除策略
假设最小值和最大值的位置分别为 l 和 r(l < r):
| 策略 | 公式 | 含义 |
|---|---|---|
| 策略1 | r + 1 | 只从左边删除,直到删掉最大值位置 |
| 策略2 | n - l | 只从右边删除,直到删掉最小值位置 |
| 策略3 | l + 1 + n - r | 左边删到最小值,右边删到最大值 |
取三者最小值即可。
解法一:递归分治法
完整代码
class Solution {
private:
struct Result {
int value;
int position;
};//定义结构体用结构体操作
// 递归找最大值及其位置
Result findMax(vector<int>& nums, int l, int r) {
if (l == r) return {nums[l], l};
int mid = (l + r) / 2;
Result left = findMax(nums, l, mid);
Result right = findMax(nums, mid + 1, r);
return (left.value >= right.value) ? left : right;
}
// 递归找最小值及其位置
Result findMin(vector<int>& nums, int l, int r) {
if (l == r) return {nums[l], l};
int mid = (l + r) / 2;
Result left = findMin(nums, l, mid);
Result right = findMin(nums, mid + 1, r);
return (left.value <= right.value) ? left : right;
}
public:
int minimumDeletions(vector<int>& nums) {
int n = nums.size();
if (n == 1) return 1;
Result maxRes = findMax(nums, 0, n - 1);
Result minRes = findMin(nums, 0, n - 1);
int l = min(minRes.position, maxRes.position);
int r = max(minRes.position, maxRes.position);
return min({
r + 1, // 只从左删
n - l, // 只从右删
l + 1 + n - r // 两边同时删
});
}
};
算法分析
时间复杂度:O(n log n)
-
递归树高度为 log n,每层需要合并左右结果
-
每个递归调用都进行常数次比较操作
-
实际比较次数:T(n) = 2T(n/2) + 1,解得 T(n) = n - 1
空间复杂度:O(log n)
-
递归调用栈的深度
-
每层需要存储左右子问题的 Result 结构
优缺点
优点:
-
展示了经典的分治思想
-
代码结构清晰,易于理解递归模式
-
适合用于教学演示分治算法
缺点:
-
实现复杂,参数传递容易出错(原代码中就有参数顺序错误)
-
递归有栈溢出风险(对于超大数组)
-
常数因子较大,实际运行速度慢
-
找最大值和最小值需要两次独立的递归遍历
解法二:哈希表法//先找值再找位置
完整代码
class Solution {
public:
int minimumDeletions(vector<int>& nums) {
int n = nums.size();
if (n == 1) return 1;
// 构建值到位置的映射
unordered_map<int, int> posMap;
for (int i = 0; i < n; i++) {
posMap[nums[i]] = i;
}
// 找最大值和最小值
int maxVal = nums[0], minVal = nums[0];
for (int num : nums) {
maxVal = max(maxVal, num);
minVal = min(minVal, num);
}
int maxIdx = posMap[maxVal];
int minIdx = posMap[minVal];
int l = min(minIdx, maxIdx);
int r = max(minIdx, maxIdx);
return min({
r + 1,
n - l,
l + 1 + n - r
});
}
};
算法分析
时间复杂度:O(n)
-
第一次遍历:构建哈希表(O(n))
-
第二次遍历:找最大最小值(O(n))
-
总共 O(n)
空间复杂度:O(n)
-
哈希表存储 n 个元素的位置
优缺点
优点:
-
查找位置是 O(1),非常快速
-
逻辑清晰,易于实现
-
适合需要多次查询位置的场景
缺点:
-
空间开销大,需要额外的 O(n) 空间
-
当数组有重复元素时,后出现的值会覆盖前面的位置(导致错误)
-
对于简单找最值的问题,使用哈希表过于复杂
-
需要两次遍历,性能不如一次遍历
解法三:STL算法法(最优解)
完整代码
class Solution {
public:
int minimumDeletions(vector<int>& nums) {
int n = nums.size();
if (n == 1) return 1;
// 直接使用 STL 算法找最值位置
int minIdx = min_element(nums.begin(), nums.end()) - nums.begin();
int maxIdx = max_element(nums.begin(), nums.end()) - nums.begin();
int l = min(minIdx, maxIdx);
int r = max(minIdx, maxIdx);
return min({
r + 1, // 只从左删
n - l, // 只从右删
l + 1 + n - r // 两边同时删
});
}
};
算法分析
时间复杂度:O(n)
-
min_element和max_element各遍历一次 -
实际上可以在一次遍历中同时找到最值位置,但 STL 分开实现
空间复杂度:O(1)
-
只使用常数个额外变量
优缺点
优点:
-
代码最简洁,只有 5-6 行核心逻辑
-
使用标准库,可靠性最高
-
不易出错,易于维护
-
空间效率最优
缺点:
-
需要遍历两次(可优化为一次)
-
依赖 STL 实现,对于特殊环境可能不适用
三种解法对比总结
| 维度 | 解法一:递归分治 | 解法二:哈希表 | 解法三:STL算法 |
|---|---|---|---|
| 时间复杂度 | O(n log n) | O(n) | O(n) |
| 空间复杂度 | O(log n) | O(n) | O(1) |
| 代码行数 | ~40 行 | ~25 行 | ~12 行 |
| 实现难度 | 较难 | 中等 | 简单 |
| 出错概率 | 高 | 中 | 低 |
| 适用场景 | 教学演示 | 多次查询 | 工程实践 |
| 处理重复元素 | ✅ 安全 | ❌ 会覆盖 | ✅ 安全 |
| 递归栈风险 | ⚠️ 存在 | ✅ 无 | ✅ 无 |
优化建议
如果追求极致性能,可以在一次遍历中同时找到最大值和最小值的位置:
class Solution {
public:
int minimumDeletions(vector<int>& nums) {
int n = nums.size();
if (n == 1) return 1;
int minIdx = 0, maxIdx = 0;
for (int i = 1; i < n; i++) {
if (nums[i] < nums[minIdx]) minIdx = i;
if (nums[i] > nums[maxIdx]) maxIdx = i;
}
int l = min(minIdx, maxIdx);
int r = max(minIdx, maxIdx);
return min({r + 1, n - l, l + 1 + n - r});
}
};
这样只需 O(n) 时间,O(1) 空间,且只遍历一次。
对于这道题:
-
解法三(STL算法)最优选择:代码简洁、安全可靠、易于维护
-
解法二(哈希表):空间浪费且有重复元素问题
-
解法一(递归分治)适合学习:展示了分治思想,但不适合实际应用
在实际工程开发中,优先使用标准库提供的功能,既能提高开发效率,又能减少 bug。


被折叠的 条评论
为什么被折叠?



