LeetCode每日一题20220818-题解-最大相等频率

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点

简单概要思路:

  1. 需要开辟一个存储空间,用来存储所有的数字出现的次数 -> 这里选用的数组配合下标实现
  2. 需要统计遍历到当前index时,最大的出现频率是多少(其他数字的频率也必须一样才可以)
  3. 需要统计有多少个数字出现频率=最大出现频率
  4. 判断是否符合条件

具体思路:

​ 遍历整个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;
    }
}

如果有更好的解题思路欢迎留言评论区

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值