持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情
题目链接:1224. 最大相等频率
题目描述
给你一个正整数数组 nums,请你帮忙从该数组中找出能满足下面要求的 最长 前缀,并返回该前缀的长度:
- 从前缀中 恰好删除一个 元素后,剩下每个数字的出现次数都相同。
如果删除这个元素后没有剩余元素存在,仍可认为每个数字都具有相同的出现次数(也就是 0 次)。
提示:
示例 1:
输入:nums = [2,2,1,1,5,3,3,5]
输出:7
解释:对于长度为 7 的子数组 [2,2,1,1,5,3,3],如果我们从中删去 nums[4] = 5,就可以得到 [2,2,1,1,3,3],里面每个数字都出现了两次。
示例 2:
输入: nums = [1,1,1,2,2,2,3,3,3,4,4,4,5]
输出: 13
整理题意
题目给定一个整数数组,让我们找到数组中 最长 的前缀,要求这个前缀满足:从前缀中 恰好删除一个 元素后,剩下每个数字的出现次数都相同。
题目规定答案最小值为
1,因为删除后数组没有元素也可以认为所有元素出现次数相同,且为0
解题思路分析
首先我们考虑需要记录的数是什么:
- 统计每个数出现的次数,因为题目求的是前缀删除一个元素后剩余元素出现次数相同。
- 统计出现次数的频数,这是因为我们可以通过频数乘以次数得到元素的个数,从而快速计算总的元素个数。
- 记录元素出现次数的最大。
在此基础上我们对不同情况进行 分类讨论:
- 当最大出现次数为
1的时候表示前缀中没有重复元素,删除任意一个元素即可。 - 当最大出现次数的频次为
1,其余元素的出现次数都为次大频率,那么此时我们可以将最大出现次数的元素删除一个,使其出现的次数成为次大频率,那么此时数组中的元素出现的次数都为次大频率。 - 除开一个数,其他所有数的出现次数都是最大出现次数,并且该数的出现次数为
1,那么可以直接删除出现次数为1的数,此时所有数的出现次数就都是最大出现次数了。
需要特别注意分清 元素出现次数 和 元素出现次数的频次 这两个东西。
具体实现
使用哈希表 count 记录每个元素出现的次数,用 freq 记录每个出现次数的频次,用 maxCount 记录元素出现次数的最大值,也就是 count 中的最大值。
- 遍历题目给定的数组
nums,假设当前访问的数为nums[i],对应地更新count[nums[i]],freq[count[nums[i]]]以及maxCount。 - 以下标
i结尾的数组前缀符合要求的充要条件为满足以下三个条件之一,也就是 分类讨论 的三种情况:- 最大出现次数
maxCount = 1:那么所有数的出现次数都是一次,随意删除一个数既可符合要求。 - 所有数的出现次数都是
maxCount或maxCount - 1,并且最大出现次数的数只有一个:删除一个最大出现次数的数,那么所有数的出现次数都是maxCount − 1。 - 除开一个数,其他所有数的出现次数都是
maxCount,并且该数的出现次数为1:直接删除出现次数为1的数,那么所有数的出现次数都是maxCount。
- 最大出现次数
优化
由于题目给定的数据范围为:,那么我们可以将哈希表 count 和 freq 直接开成大小为 的数组,这是因为数组的查询时间为标准的 ,而哈希表的查询数据为常数级别的 ,这样可以在一定程度上对时间复杂度进行优化。
复杂度分析
- 时间复杂度:,其中
n是数组nums的长度。遍历数组nums需要 。 - 空间复杂度:。保存两个哈希表需要 的空间。
代码实现
class Solution {
public:
int maxEqualFreq(vector<int>& nums) {
// maxCount 记录出现次数中的最大值,也就是 count 中的最大值
int maxCount = 0, n = nums.size(), ans = 0;
int count[100001], freq[100001];
memset(count, 0, sizeof(count));
memset(freq, 0, sizeof(freq));
for(int i = 0; i < n; i++){
// 如果 nums[i] 之前出现过
if(count[nums[i]] > 0) freq[count[nums[i]]]--;
// 增加 nums[i] 出现次数
count[nums[i]]++;
// 维护 maxCount 的值
maxCount = max(maxCount, count[nums[i]]);
// 维护频次
freq[count[nums[i]]]++;
//
bool check =
// 最大出现次数为 1,任意删除一个数即可
(maxCount == 1) ||
// 最大出现次数的个数 + 次大出现次数的个数 = i + 1 并且 最大出现次数的个数为 1
((freq[maxCount] * maxCount + freq[maxCount - 1] * (maxCount - 1) == i + 1)
&& (freq[maxCount] == 1)) ||
// 最大出现次数的个数 + 1 = i + 1 并且出现次数为 1 次的有一个
((freq[maxCount] * maxCount + 1 == i + 1) && (freq[1] == 1));
if(check) ans = i + 1;
}
return ans;
}
};
总结
- 该题有点套娃的感觉,比较绕和裹搅,需要理清 元素出现次数 和 元素出现次数的频次 这两个东西。
- 该题的难点在于 分类讨论 上,对于不同情况进行分类讨论,还要保证不重不漏是比较难的。
- 测试结果:
可以很明显的看到数组优化后在时间复杂度上的明显差异。
结束语
很多人遇到挫折时,经常会说:“如果当初怎样怎样,现在就不是这般场景了。”可时光无法倒流,没人能够改变过去。唯有把握好当下,才是对过往最好的答复。人生没有如果,愿你不纠结过去,不畏惧未来。新的一天,加油!