Medium
题目描述
给你一个下标从 0 开始的整数数组 nums 和一个整数 k。
如果一个子数组中的所有元素都相等,则称该子数组为 相等子数组。注意,空子数组也是相等子数组。
返回在从 nums 中删除最多 k 个元素后,可能的最长相等子数组的长度。
子数组是数组中一个连续的可能为空的元素序列。
示例 1:
输入:nums = [1,3,2,3,1,3], k = 3
输出:3
解释:最优方案是删除下标 2 和下标 4 的元素。
删除后,nums 变为 [1, 3, 3, 3]。
最长相等子数组从 i = 1 开始到 j = 3 结束,长度等于 3。
可以证明无法创建更长的相等子数组。
示例 2:
输入:nums = [1,1,2,2,1,1], k = 2
输出:4
解释:最优方案是删除下标 2 和下标 3 的元素。
删除后,nums 变为 [1, 1, 1, 1]。
数组本身就是一个相等子数组,所以答案是 4。
可以证明无法创建更长的相等子数组。
提示:
1 <= nums.length <= 10^51 <= nums[i] <= nums.length0 <= k <= nums.length
解题思路
解题思路
这道题的关键洞察是:要形成最长的相等子数组,我们需要选择某个特定的数值,然后在包含这个数值的所有位置中选择一个连续的区间,使得删除区间内其他数值后剩余的相等元素最多。
核心思想
按数值分组:首先将数组中相同数值的所有下标收集起来,存储在哈希表中。
滑动窗口:对于每个数值的下标列表,使用滑动窗口找到最优的区间。关键是理解:如果我们选择下标列表中从位置
i到位置j的区间,那么原数组中对应的区间是[indices[i], indices[j]],其中包含indices[j] - indices[i] + 1个元素,但只有j - i + 1个是我们想要的数值,所以需要删除(indices[j] - indices[i] + 1) - (j - i + 1) = indices[j] - indices[i] - (j - i)个元素。约束条件:我们需要保证删除的元素数量不超过
k,即indices[j] - indices[i] - (j - i) <= k。窗口调整:当删除元素数量超过
k时,收缩左边界;否则扩展右边界,并更新最大长度。
这种方法的时间复杂度是 O(n),空间复杂度是 O(n),是最优解法。
代码实现
class Solution {
public:
int longestEqualSubarray(vector<int>& nums, int k) {
unordered_map<int, vector<int>> pos;
// 收集每个数值的所有位置
for (int i = 0; i < nums.size(); i++) {
pos[nums[i]].push_back(i);
}
int maxLen = 0;
// 对每个数值使用滑动窗口
for (auto& [val, indices] : pos) {
int left = 0;
for (int right = 0; right < indices.size(); right++) {
// 当前窗口需要删除的元素数量
while (indices[right] - indices[left] - (right - left) > k) {
left++;
}
maxLen = max(maxLen, right - left + 1);
}
}
return maxLen;
}
};
class Solution:
def longestEqualSubarray(self, nums: List[int], k: int) -> int:
from collections import defaultdict
pos = defaultdict(list)
# 收集每个数值的所有位置
for i, num in enumerate(nums):
pos[num].append(i)
max_len = 0
# 对每个数值使用滑动窗口
for indices in pos.values():
left = 0
for right in range(len(indices)):
# 当前窗口需要删除的元素数量
while indices[right] - indices[left] - (right - left) > k:
left += 1
max_len = max(max_len, right - left + 1)
return max_len
public class Solution {
public int LongestEqualSubarray(IList<int> nums, int k) {
var pos = new Dictionary<int, List<int>>();
// 收集每个数值的所有位置
for (int i = 0; i < nums.Count; i++) {
if (!pos.ContainsKey(nums[i])) {
pos[nums[i]] = new List<int>();
}
pos[nums[i]].Add(i);
}
int maxLen = 0;
// 对每个数值使用滑动窗口
foreach (var indices in pos.Values) {
int left = 0;
for (int right = 0; right < indices.Count; right++) {
// 当前窗口需要删除的元素数量
while (indices[right] - indices[left] - (right - left) > k) {
left++;
}
maxLen = Math.Max(maxLen, right - left + 1);
}
}
return maxLen;
}
}
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var longestEqualSubarray = function(nums, k) {
const pos = new Map();
// 收集每个数值的所有位置
for (let i = 0; i < nums.length; i++) {
if (!pos.has(nums[i])) {
pos.set(nums[i], []);
}
pos.get(nums[i]).push(i);
}
let maxLen = 0;
// 对每个数值使用滑动窗口
for (const indices of pos.values()) {
let left = 0;
for (let right = 0; right < indices.length; right++) {
// 当前窗口需要删除的元素数量
while (indices[right] - indices[left] - (right - left) > k) {
left++;
}
maxLen = Math.max(maxLen, right - left + 1);
}
}
return maxLen;
};
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
时间复杂度:O(n),其中 n 是数组长度。需要遍历数组一次收集位置信息,然后对每个数值的位置列表进行滑动窗口操作,总的遍历次数不超过 n。
空间复杂度:O(n),用于存储每个数值对应的位置列表。