2831-找出最长等值子数组

Raphael Liu Lv10

给你一个下标从 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 <= 105
  • 1 <= nums[i] <= nums.length
  • 0 <= k <= nums.length

请看 视频讲解 第四题。

前置知识:同向双指针

详见【基础算法精讲】

思路

把相同元素分组,相同元素的下标记录到哈希表(或者数组)pos 中。

遍历 pos 中的每个下标列表 ps,用双指针处理:

如果等值子数组的元素下标是从 ps}[\textit{left}] 到 ps}[\textit{right}],那么子数组的长度为

\textit{ps}[\textit{right}] - \textit{ps}[\textit{left}] + 1

其中无需删除的元素个数为

\textit{right} - \textit{left} + 1

那么需要删除的元素个数为

\textit{ps}[\textit{right}] - \textit{ps}[\textit{left}] - (\textit{right} - \textit{left})

如果上式大于 k,则需要移动左指针。

满足条件时,用 right}-\textit{left}+1 更新答案的最大值。

代码实现时,可以在哈希表中记录 ps}[i]-i,这样删除的元素个数就是

\textit{ps}[\textit{right}] - \textit{ps}[\textit{left}]

[sol-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution:
def longestEqualSubarray(self, nums: List[int], k: int) -> int:
pos = [[] for _ in range(len(nums) + 1)]
for i, x in enumerate(nums):
pos[x].append(i - len(pos[x]))
ans = 0
for ps in pos:
if len(ps) <= ans: continue
left = 0
for right, p in enumerate(ps):
while p - ps[left] > k: # 要删除的数太多了
left += 1
ans = max(ans, right - left + 1)
return ans
[sol-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int longestEqualSubarray(List<Integer> nums, int k) {
int n = nums.size(), ans = 0;
List<Integer>[] pos = new ArrayList[n + 1];
Arrays.setAll(pos, e -> new ArrayList<>());
for (int i = 0; i < n; i++) {
int x = nums.get(i);
pos[x].add(i - pos[x].size());
}
for (var ps : pos) {
if (ps.size() <= ans) continue;
int left = 0;
for (int right = 0; right < ps.size(); right++) {
while (ps.get(right) - ps.get(left) > k) // 要删除的数太多了
left++;
ans = Math.max(ans, right - left + 1);
}
}
return ans;
}
}
[sol-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int longestEqualSubarray(vector<int> &nums, int k) {
int n = nums.size(), ans = 0;
vector<vector<int>> pos(n + 1);
for (int i = 0; i < n; i++)
pos[nums[i]].push_back(i - pos[nums[i]].size());
for (auto &ps: pos) {
if (ps.size() <= ans) continue;
int left = 0;
for (int right = 0; right < ps.size(); right++) {
while (ps[right] - ps[left] > k) // 要删除的数太多了
left++;
ans = max(ans, right - left + 1);
}
}
return ans;
}
};
[sol-Go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func longestEqualSubarray(nums []int, k int) (ans int) {
pos := make([][]int, len(nums)+1)
for i, x := range nums {
pos[x] = append(pos[x], i-len(pos[x]))
}
for _, ps := range pos {
if len(ps) <= ans {
continue
}
left := 0
for right, p := range ps {
for p-ps[left] > k { // 要删除的数太多了
left++
}
ans = max(ans, right-left+1)
}
}
return
}

func max(a, b int) int { if b > a { return b }; return a }

复杂度分析

  • 时间复杂度:\mathcal{O}(n),其中 n 为 nums 的长度。
  • 空间复杂度:\mathcal{O}(n)。

思考题

把「删除 k 个数」改成「修改 k 个数」要怎么做?

 Comments
On this page
2831-找出最长等值子数组