LeetCode No.1224 最大相等频率(Hard)

本文介绍了LeetCode难题No.1224,要求找到数组中最长的前缀,使得删除一个元素后所有数字出现频率相同。通过桶排和动态维护频率来解决,重点在于如何判断满足条件的前缀。文章提供了详细的解题思路和高效代码实现。
摘要由CSDN通过智能技术生成

题目描述


给出一个正整数数组 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

示例 3:

输入:nums = [1,1,1,2,2,2]
输出:5

示例 4:

输入:nums = [10,2,8,9,3,8,1,5,2,3,7,6]
输出:8

提示:

2 <= nums.length <= 10^5
1 <= nums[i] <= 10^5

解题思考

一开始想的还是利用桶排来做这道题。

题目的关键是每个数字出现的频率,同时数字的数量已经确定了,那么筒式排序就可以直接用上了。创建一个大小为100000的数组cnt[100000],用于存放每个数字出现的次数。

其实,让题目难度增加的关键词只有一个,那就是前缀。题目要求找到最长前缀,那最直观的思路就是从前往后找,尽量遍历一遍数组,直接能够找到最长前缀。那么怎么遍历呢,这就是最难的地方。下面我来介绍一下思路:

  • 其实,我们找到的最长前缀无非只有两种可能:
    1. 其他的数字都有相同频率,只有一个数字出现的次数比其他的多1;
    2. 其他的数字都有相同频率,只有一个数字只出现1次。
  • 根据上面的思路,我们只需要找到两个条件,更新长度:
    • 条件1: 当其他数字出现的次数都为n,而新读入的节点的数字出现的总次数为n+1
    • 条件2: 当其他数字出现的次数都为n,而新读入的节点的数字第1次出现。
  • 只有当遇到上面两种情况,更新最长前缀的长度,其他时候不做任何操作。

主要思路出现了,那我们怎么判断其他数字出现的次数都为n,而新读入的节点的次数为n+1或者1呢?

这个时候,我们可以再声明一个数组,保存频率出现的次数。例如nums = {1, 1, 1, 2, 2, 2, 2}。我们每读入一个节点nums[n]cnt[nums[n]]++,声明一个freq[100000],保存每个cnt出现的次数,则freq[cnt[nums[n]]]++。与此同时,因为我们要找到出现频率最多的那个数字,因此令maxn = max(cnt)

用上面的nums举个例子

  • n = 5时 ,maxn = 3,而cnt[1] = 3, cnt[2] = 3, freq[cnt[2]] = freq[cnt[1]] = 2freq[3] = 2说明数量为3的数字有两个。至此,读入的总数字数量为freq[3] * 2 = 6
  • n = 6时,maxn = 4,开始进行判断。如果freq[4] = 1(说明最大出现次数是第一次出现,符合上面条件1的后半部分),判断freq[maxn - 1] * (maxn - 1) = n(出现maxn - 1次的数字的总数量 = 除当下数字之外的数字总数,即,其他数字频率一样。符合上面条件1的前半部分),那么1条件都满足,更新长度。
  • 又假设nums = {1, 1, 1, 2, 2, 2, 3},当n = 6时,maxn = 3,开始进行判断。此时freq[maxn] * maxn = n,即除了当下的数,其他数出现的频率相同,而且当下的数出现的次数仅为1。因此,满足条件2,更新长度。

上面的思路的关键是freq, maxn的正确应用,思路不是很好想,也基本符合Hard的难度。我试过用另一个思路去做这道题,但是运行时间很长,通过所有用例的运行总时长是这个思路的30倍,我就不放出来献丑了。


代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    static int maxEqualFreq(vector<int> &nums) {
        int cnt[100000] = {0}, freq[100000] = {0};
        int ans = 0, maxn = 0;
        int i = 0;
        while (i < nums.size()) {
            maxn = max(maxn, ++cnt[nums[i]]);
            freq[cnt[nums[i]]]++;

            if (freq[maxn] == 1 && freq[maxn - 1] * (maxn - 1) == i) {
                // 只有一个最大出现次数
                ans = i;
            } else if (freq[maxn] * maxn == i) {
                // 如果数字出现的频率都一样
                ans = i;
            }
            i++;
        }
        int result = maxn == 1 ? nums.size() : ans + 1;
        return result;
    }
};

int main() {
    vector<int> s = {2,2,2,1,1,1,5,3,3,5};
    int result;
    Solution solution;
    result = solution.maxEqualFreq(s);
    cout << result;
}

总结

这个思路非常有意思,因而虽然难度为HARD,但是实际上代码非常简洁,同时时间复杂度为 O ( n ) O(n) O(n) ( n n n是数组长度)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值