一、题目
给你一个正整数数组 nums
,请你帮忙从该数组中找出能满足下面要求的 最长 前缀,并返回该前缀的长度:
从前缀中 恰好删除一个 元素后,剩下每个数字的出现次数都相同。
如果删除这个元素后没有剩余元素存在,仍可认为每个数字都具有相同的出现次数(也就是 0 次)。
二、示例
2.1> 示例 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.2> 示例 2:
【输入】nums = [1,1,1,2,2,2,3,3,3,4,4,4,5]
【输出】13
提示:
2
<= nums.length <=10^5
1
<= nums[i] <=10^5
三、解题思路
根据题目描述,我们需要找到一个最长的前缀,这个前缀要满足剩下的每个数字出现的次数都相同,那么我们其实可以根据这种要求来归类一下,大致有如下四种类型,是可以满足题目要求的。
首先,如果我们的前缀字符串都是不同的,并且出现的次数都是一次的话,那么我们无论是删除哪一个元素,剩余的元素的出现次数也都是1次,所以,这种类型我们可以通过计算每个元素的出现次数来做判断。那么,怎么能快速的确定每个元素的出现次数,并且可以快速判断出来出现次数为1的元素个数呢?这里面我们就需要两个变量了,一个用来存储元素和它出现次数的关系——elementTimes
,另一个用来存储出现了第N次的元素的个数——timesGroup
。那么当出现了第1次的元素个数等于当前遍历的个数的时候,就表示当前字符串中的元素,满足此类型。关于类型一,请参见下图:
其次,第二种类型的特征是,只有一个元素出现了N次,而其他的元素都只出现了N-1次。那么当我们将出现了N次的元素移除了一个之后,那么剩余的字符串中的所有元素,出现次数都是N-1次了。这里面我们通过引入一个参数——maxTimes
,用于记录最大出现次数。每次遍历元素并计算其出现次数的时候,我们都可以采用Math的max(...)
方法来对maxTimes进行同步更新。那么,我们就可以通过获取timesGroup[maxTimes]是否等于1
并且计算 timesGroup[maxTimes - 1] * (maxTimes - 1) + 1是否等于遍历的元素个数总和
,来确定是否满足类型二的条件。关于类型二,请参见下图:
那么,第三种类型的特征就是,只有一个元素出现过1次,其他的元素,都出现过N次,那么这时候,当我们移除了仅仅出现过一次的元素时,自然剩下的所有元素,都是出现过N次的。这种情况,我们通过计算出现过N次元素的总和,即:timesGroup[maxTimes] * maxTimes + 1是否等于总遍历元素总和
来判断是否满足类型三。关于类型三,请参见下图:
最后一种类型,就是一个元素出现了N次,并且只有这个元素出现了。那么,针对这种类型,无论去除哪个,结果都是满足条件的。关于类型四,其实在类型二的判断逻辑中,就已经包含了。所以,在代码实现中,就不用单独做处理了。关于类型四,请参见下图:
四、代码实现
4.1> 实现1:哈希Map + 最大次数
class Solution {
public static int maxEqualFreq(int[] nums) {
/**
* 情况一:所有元素均出现1次——[1,2,3,4,5,6]
* 情况二:多个出现N次和1个出现N+1次元素——[1,1,2,2,3,3,3]
* 情况三:去除1个元素,就满足"最大出现次数"*"元素个数"——[2,2,2,1,1,1,3]、[1,1,1,1,1]、[1,1,1,1,2]
*/
Map<Integer, Integer> elementTimes = new HashMap(); // 记录元素出现的次数,即:key=[元素1] value=[出现2次]
Map<Integer, Integer> timesGroup = new HashMap(); // 记录某次数中存在的元素个数,即:key=[出现2次] value=[3个元素满足]
int maxLength = 0, maxTimes = 0;
for (int i = 0; i < nums.length; i++) {
int num = elementTimes.getOrDefault(nums[i], 0);
elementTimes.put(nums[i], num + 1);
if (num > 0) timesGroup.put(num, timesGroup.get(num) - 1);
timesGroup.put(num + 1, timesGroup.getOrDefault(num + 1, 0) + 1);
maxTimes = Math.max(maxTimes, num + 1);
if (timesGroup.get(1) == i + 1 || // 情况一
(timesGroup.get(maxTimes) == 1 && maxTimes + timesGroup.get(maxTimes - 1) * (maxTimes - 1) == i + 1) || // 情况二
(maxTimes == i + 1) || // 情况三-1
timesGroup.get(maxTimes) * maxTimes == i // 情况三-2
) maxLength = i + 1;
}
return maxLength;
}
}
4.2> 实现2:哈希Map + 最大次数
class Solution {
public static int maxEqualFreq(int[] nums) {
/**
* 情况一:所有元素均出现1次——[1,2,3,4,5,6]
* 情况二:只有1个元素出现N次 以及 多个出现N次和1个出现N+1次元素——[1,1,1,1]、[1,1,2,2,3,3,3]
* 情况三:去除1个元素,就满足"最大出现次数"*"元素个数"——[2,2,2,1,1,1,3]、[1,1,1,1,1]、[1,1,1,1,2]
*/
int[] elementTimes = new int[100001]; // 记录元素出现的次数,即:index=[元素1] elementTimes[index]=[出现2次]
int[] timesGroup = new int[100001]; // 记录某次数中存在的元素个数,即:index=[出现2次] timesGroup[index]=[3个元素满足]
Arrays.fill(elementTimes, 0);
Arrays.fill(timesGroup, 0);
int maxLength = 0, maxTimes = 0;
for (int i = 0; i < nums.length; i++) {
int num = elementTimes[nums[i]];
elementTimes[nums[i]] += 1;
if (num > 0) timesGroup[num] += -1;
timesGroup[num + 1] += 1;
maxTimes = Math.max(maxTimes, num + 1);
if (timesGroup[1] == i + 1 || // 情况一
(timesGroup[maxTimes] == 1 && maxTimes + timesGroup[maxTimes - 1] * (maxTimes - 1) == i + 1) || // 情况二
timesGroup[maxTimes] * maxTimes == i // 情况三
) maxLength = i + 1;
}
return maxLength;
}
}
今天的文章内容就这些了:
写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。
更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」