Hard
题目描述
给你一个包含不同数字的整数数组 nums,你可以执行以下操作直到数组为空:
- 如果第一个元素是最小值,则将其移除
- 否则,将第一个元素移到数组末尾
返回一个整数,表示使 nums 变为空数组所需的操作次数。
示例 1:
输入:nums = [3,4,-1]
输出:5
解释:
操作 1: [4, -1, 3]
操作 2: [-1, 3, 4]
操作 3: [3, 4]
操作 4: [4]
操作 5: []
示例 2:
输入:nums = [1,2,4,3]
输出:5
解释:
操作 1: [2, 4, 3]
操作 2: [4, 3]
操作 3: [3, 4]
操作 4: [4]
操作 5: []
示例 3:
输入:nums = [1,2,3]
输出:3
解释:
操作 1: [2, 3]
操作 2: [3]
操作 3: []
提示:
1 <= nums.length <= 10^5-10^9 <= nums[i] <= 10^9nums中的所有值都不相同
解题思路
这道题的关键是理解元素的移除顺序:我们必须按照从小到大的顺序移除元素。
思路分析:
核心观察:元素必须按值的升序被移除。每次只能移除当前位于数组开头的最小元素。
模拟过程:
- 将数组按值排序,得到移除顺序
- 对于每个要移除的元素,计算需要多少次操作才能将其移动到数组开头
- 关键是计算当前元素前面还有多少个未被移除的元素
优化方法:
- 使用索引映射记录每个值在原数组中的位置
- 按值排序后,依次处理每个元素
- 使用数据结构(如树状数组或有序集合)快速计算当前位置前面剩余元素的数量
计算公式:
- 对于每个元素,操作数 = 当前位置前面剩余元素数量 + 1(移除操作)
- 需要考虑数组的循环特性:如果当前元素位置小于上一个被移除元素的位置,说明发生了"绕一圈"
推荐解法:使用有序集合(TreeSet/SortedList)来维护剩余元素的索引,可以高效地查询和删除。
代码实现
class Solution {
public:
long long countOperationsToEmptyArray(vector<int>& nums) {
int n = nums.size();
vector<pair<int, int>> sorted_nums;
for (int i = 0; i < n; i++) {
sorted_nums.push_back({nums[i], i});
}
sort(sorted_nums.begin(), sorted_nums.end());
set<int> remaining_indices;
for (int i = 0; i < n; i++) {
remaining_indices.insert(i);
}
long long operations = 0;
int last_pos = 0;
for (auto& [val, idx] : sorted_nums) {
auto it = remaining_indices.find(idx);
int current_pos = distance(remaining_indices.begin(), it);
if (current_pos >= last_pos) {
operations += current_pos - last_pos + 1;
} else {
operations += remaining_indices.size() - last_pos + current_pos + 1;
}
last_pos = current_pos;
remaining_indices.erase(it);
}
return operations;
}
};
class Solution:
def countOperationsToEmptyArray(self, nums: List[int]) -> int:
n = len(nums)
sorted_nums = sorted((val, idx) for idx, val in enumerate(nums))
from sortedcontainers import SortedList
remaining_indices = SortedList(range(n))
operations = 0
last_pos = 0
for val, idx in sorted_nums:
current_pos = remaining_indices.index(idx)
if current_pos >= last_pos:
operations += current_pos - last_pos + 1
else:
operations += len(remaining_indices) - last_pos + current_pos + 1
last_pos = current_pos
remaining_indices.remove(idx)
return operations
public class Solution {
public long CountOperationsToEmptyArray(int[] nums) {
int n = nums.Length;
var sortedNums = nums.Select((val, idx) => new { Value = val, Index = idx })
.OrderBy(x => x.Value)
.ToList();
var remainingIndices = new SortedSet<int>();
for (int i = 0; i < n; i++) {
remainingIndices.Add(i);
}
long operations = 0;
int lastPos = 0;
foreach (var item in sortedNums) {
var remainingList = remainingIndices.ToList();
int currentPos = remainingList.IndexOf(item.Index);
if (currentPos >= lastPos) {
operations += currentPos - lastPos + 1;
} else {
operations += remainingIndices.Count - lastPos + currentPos + 1;
}
lastPos = currentPos;
remainingIndices.Remove(item.Index);
}
return operations;
}
}
/**
* @param {number[]} nums
* @return {number}
*/
var countOperationsToEmptyArray = function(nums) {
const n = nums.length;
const sortedNums = nums.map((val, idx) => [val, idx]).sort((a, b) => a[0] - b[0]);
const remainingIndices = new Set();
for (let i = 0; i < n; i++) {
remainingIndices.add(i);
}
let operations = 0;
let lastPos = 0;
for (const [val, idx] of sortedNums) {
const remainingArray = Array.from(remainingIndices).sort((a, b) => a - b);
const currentPos = remainingArray.indexOf(idx);
if (currentPos >= lastPos) {
operations += currentPos - lastPos + 1;
} else {
operations += remainingIndices.size - lastPos + currentPos + 1;
}
lastPos = currentPos;
remainingIndices.delete(idx);
}
return operations;
};
复杂度分析
| 复杂度类型 | 大O表示法 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 排序需要O(n log n),每次查找和删除操作需要O(log n) |
| 空间复杂度 | O(n) | 需要额外的数据结构存储排序后的数组和剩余索引 |