1224. 最大相等频率
本题为 2022-08-18 每日一题
难度:【困难】
题目链接:https://leetcode.cn/problems/maximum-equal-frequency/
题目
给你一个正整数数组 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
题解
本题存在一定难度,故本文也是在下午才完成编写,将会带入个人的思考流程以及理解他人思路的方式讲解该题目。
解法一
对于数组求前缀长度,通常能够想到的第一个思路就是动态规划,寻找dp公式。但是从题目含义来看,dp[i]和dp[i-1]等似乎并没有什么关系,于是先放弃动态规划,尝试直接用较为暴力的思路去解答本题 —— 遍历整个数组,用某种方式存储已经遍历的数值,遍历每位数值时都判断一下是否符合题目要求。
那么本题的关键就在于 -> 什么前缀才符合题目要求
虽然感觉这样有点为了做题而做题,但确实是在解析题目。
第一种情况:[1, 2, 3, 4, 5] 每个数字
出现且 出现频率
只有1次
第二种情况:[1111, 2222, 3333, 4] 其他数字出现频率相同
,只多出1个数字
第三种情况:[1, 1, 1, 1, 1] 只有一种数字
如上述情况,我把关键的字眼已经特别标记出来了,那么我们在整理思路的时候也要着重考虑这3点
简单概要思路:
- 需要开辟一个存储空间,用来存储所有的数字出现的次数 -> 这里选用的数组配合下标实现
- 需要统计遍历到当前index时,最大的出现频率是多少(其他数字的频率也必须一样才可以)
- 需要统计有多少个数字出现频率=最大出现频率
- 判断是否符合条件
具体思路:
遍历整个nums,对于每一位,都去统计他的出现次数,如果他的出现次数>当前已知的最大次数(也就是频率),那么就更改最大频率为这个数字出现的次数,同时还要重置已经统计好的满足出现频率=最大频率
的计数器(因为最大频率变了,那之前相等的肯定就不相等了)。反而如果出现次数=最大频率
,那么就让计数器+1(不用考虑会不会重复加的情况,如果频率不相等那就不会+1;如果现在相等了然后最大频率变了,那计数器会重置)。最后再去判断当前的数字种类、出现频率、最大频率
是否达到题目要求,并赋值给一个result
就可以了。如果看完解法一的代码感觉云里雾里,那么立刻看解法二便会豁然开朗。
/**
* 执行用时:6 ms, 在所有 Java 提交中击败了88.89%的用户
* 内存消耗:50.8 MB, 在所有 Java 提交中击败了42.22%的用户
*/
class Solution {
public int maxEqualFreq(int[] nums) {
// 开辟一个计数器 注意数据范围: 1 <= nums[i] <= 100000
int[] cnt = new int[100001];
// 记录最大出现频率
int maxFreq = 0;
// 记录满足频率=最大频率的数字个数,以及全部数字类型的个数
int maxType = 0, type = 0;
// 记录result
int res = 0;
// 按位遍历整个nums
for (int i = 0; i < nums.length; i++) {
// 获取当前下标的值,因为后面常用所以提取出来
int num = nums[i];
// 这一步用来判断是否为新的数字,便于统计不同数字的个数
if (cnt[num] == 0) {
type++;
}
// 计数器++
cnt[num]++;
// 计数器++之后,判断一下最大频率的问题
// 如果当前数值的频率更大,那么需要重置一下
if (cnt[num] > maxFreq) {
maxFreq = cnt[num];
maxType = 1;
} else if (cnt[num] == maxFreq) {
// 如果恰好达到最大频率,那说明该数字在当前坐标是符合题目需求的
maxType++;
}
// 判断
int t = check(maxFreq, maxType, type, i);
// res不一定发生改变
res = t == -1 ? res : t;
}
return res;
}
// 该方法主要是进行分情况讨论
// 之所以返回idx+1 是为了确保能够“恰好删除一个元素后“符合题意
int check(int maxFreq, int maxType, int type, int idx) {
if (maxFreq == 1) {
// 情况1 -> [1,2,3,4,5,6,8] 这种数组
return idx + 1;
} else if (maxFreq * maxType == idx) {
// 情况2 -> [1,1,2,2,3,3,4,4] 这种数组 -> maxFreq=2 maxType=4 idx=8
return idx + 1;
} else if (maxType == 1 && maxFreq == idx / type + 1) {
// 情况3 -> [1,1,1,2,2,2,2] 也就是某个数字的频率=其他数字频率+1(其他数字频率相等)
return idx + 1;
} else {
// 只有上述3种合理,此处返回-1表示条件不满足,避免res改变
return -1;
}
}
}
解法二
从解法一看出,解决本题的关键在于统计数字出现的频率,解法二使用哈希表存储每个频率含有的数字个数。
class Solution {
public int maxEqualFreq(int[] nums) {
// 每个数字的计数器
int[] cnt = new int[100001];
// 频率统计
Map<Integer, Integer> freq = new HashMap<>();
int maxCount = 0;
int result = 0;
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
// 数字计数器频率自增
cnt[num]++;
int times = cnt[num];
// 某数字的频率增加了,那原来频率对应的数字种类就减少,对应的新频率种类增加
if (times > 1) {
freq.put(times - 1, freq.get(times - 1) - 1);
}
freq.put(times, freq.getOrDefault(times, 0) + 1);
// 变更最大频率
maxCount = Math.max(maxCount, times);
if (maxCount == 1 ||
maxCount * freq.get(maxCount) == i ||
(maxCount - 1) * (freq.getOrDefault(maxCount - 1, 0) + 1) == i) {
result = i + 1;
}
}
return result;
}
}
解法三
本代码原作者:https://leetcode.cn/u/ac_oier/
该解法是解法二的升级版->用数组代替哈希表,其余内容基本不变。使用数组的好处仍然是时间消耗更少(减少了hash运算和产生碰撞带来的时间消耗)
代码如下:
/**
* 执行用时:11 ms, 在所有 Java 提交中击败了73.33%的用户
* 内存消耗:51 MB, 在所有 Java 提交中击败了41.11%的用户
*/
class Solution {
public int maxEqualFreq(int[] nums) {
int[] cnt = new int[100001];
int[] freq = new int[100001];
int maxFreq = 0, res = 0;
for (int i = 0; i < nums.length; i++) {
int t = nums[i], cur = ++cnt[t], len = i + 1;
freq[cur]++;
freq[cur - 1]--;
maxFreq = Math.max(maxFreq, cur);
if (maxFreq == 1)
res = len;
if (maxFreq * freq[maxFreq] + 1 == len)
res = len;
if ((maxFreq - 1) * (freq[maxFreq - 1] + 1) + 1 == len)
res = len;
}
return res;
}
}
如果有更好的解题思路欢迎留言评论区