哈希表题目:最大相等频率

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
  • 解法
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:最大相等频率

出处:1224. 最大相等频率

难度

8 级

题目描述

要求

给你一个正整数数组 nums \texttt{nums} nums,请你从该数组中找出符合下面要求的最长前缀的长度:从前缀中恰好删除一个元素后,剩下每个数字的出现次数都相同。

如果删除一个元素后没有剩余元素,仍可认为每个数字都具有相同的出现次数( 0 \texttt{0} 0 次)。

示例

示例 1:

输入: nums   =   [2,2,1,1,5,3,3,5] \texttt{nums = [2,2,1,1,5,3,3,5]} nums = [2,2,1,1,5,3,3,5]
输出: 7 \texttt{7} 7
解释:对于长度为 7 \texttt{7} 7 的子数组 [2,2,1,1,5,3,3] \texttt{[2,2,1,1,5,3,3]} [2,2,1,1,5,3,3],如果我们删去 nums[4]   =   5 \texttt{nums[4] = 5} nums[4] = 5,就可以得到 [2,2,1,1,3,3] \texttt{[2,2,1,1,3,3]} [2,2,1,1,3,3],每个数字都出现了两次。

示例 2:

输入: nums   =   [1,1,1,2,2,2,3,3,3,4,4,4,5] \texttt{nums = [1,1,1,2,2,2,3,3,3,4,4,4,5]} nums = [1,1,1,2,2,2,3,3,3,4,4,4,5]
输出: 13 \texttt{13} 13

数据范围

  • 2 ≤ nums.length ≤ 10 5 \texttt{2} \le \texttt{nums.length} \le \texttt{10}^\texttt{5} 2nums.length105
  • 1 ≤ nums[i] ≤ 10 5 \texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{5} 1nums[i]105

解法

思路和算法

为了在数组 nums \textit{nums} nums 中找到符合要求的最长前缀的长度,需要遍历数组 nums \textit{nums} nums 的每一个前缀并分别判断是否符合要求。假设数组 nums \textit{nums} nums 的长度是 n n n,由于 n n n 的最大值是 1 0 5 10^5 105,因此需要使用 O ( n ) O(n) O(n) 的解法,每次判断前缀是否符合要求的时间复杂度应该是 O ( 1 ) O(1) O(1)

假设一个符合要求的前缀的长度是 m m m,则在该前缀中存在一个元素,删除该元素之后剩下的 m − 1 m - 1 m1 个元素满足有 x x x 个不同的元素且这 x x x 个不同的元素各出现 y y y 次,即 x × y = m − 1 x \times y = m - 1 x×y=m1

m = 1 m = 1 m=1 时,有 y = 0 y = 0 y=0。当 m > 1 m > 1 m>1 时, m − 1 > 0 m - 1 > 0 m1>0,因此 x x x y y y 都是正整数且都在范围 [ 1 , m − 1 ] [1, m - 1] [1,m1] 内。可能的情况有如下 3 3 3 种。

  1. x = 1 x = 1 x=1 y = m − 1 y = m - 1 y=m1,此时 m − 1 m - 1 m1 个元素全部相同。

  2. x = m − 1 x = m - 1 x=m1 y = 1 y = 1 y=1,此时 m − 1 m - 1 m1 个元素各不相同。

  3. 1 < x < m − 1 1 < x < m - 1 1<x<m1 y = m − 1 x y = \dfrac{m - 1}{x} y=xm1,此时 m − 1 m - 1 m1 个元素不完全相同且每个元素的出现频率都大于 1 1 1

对于上述 3 3 3 种情况,分别考虑被删除的元素的可能取值,以及对应的 m m m 个元素中的不同元素个数和每个元素的出现频率。

  1. x = 1 x = 1 x=1 y = m − 1 y = m - 1 y=m1

    • 如果被删除的元素等于其余的 x x x 个元素,则 m m m 个元素中有 1 1 1 个不同元素,出现 m m m 次;

    • 如果被删除的元素不等于其余的 x x x 个元素,则 m m m 个元素中有 2 2 2 个不同元素,分别出现 m − 1 m - 1 m1 次和 1 1 1 次。

  2. x = m − 1 x = m - 1 x=m1 y = 1 y = 1 y=1

    • 如果被删除的元素等于其余的 x x x 个元素中的一个,则 m m m 个元素中有 m − 1 m - 1 m1 个不同元素,其中一个元素出现 2 2 2 次,其余元素各出现 1 1 1 次;

    • 如果被删除的元素和其余的 x x x 个元素都不相等,则 m m m 个元素中有 m m m 个不同元素,各出现 1 1 1 次。

  3. 1 < x < m − 1 1 < x < m - 1 1<x<m1 y = m − 1 x y = \dfrac{m - 1}{x} y=xm1

    • 如果被删除的元素等于其余的 x x x 个元素中的一个,则 m m m 个元素中有 x x x 个不同元素,其中一个元素出现 y + 1 y + 1 y+1 次,其余元素各出现 y y y 次;

    • 如果被删除的元素和其余的 x x x 个元素都不相等,则 m m m 个元素中有 x + 1 x + 1 x+1 个不同元素,其中一个元素出现 1 1 1 次,其余元素各出现 y y y 次。

整理上述列举的全部情况, m m m 个元素的可能情况有如下 4 4 4 种。

  1. 1 1 1 个不同元素,出现 m m m 次。例如 [ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ] [1,1,1,1,1,1,1,1,1] [1,1,1,1,1,1,1,1,1]

  2. m m m 个不同元素,各出现 1 1 1 次。例如 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] [1,2,3,4,5,6,7,8,9] [1,2,3,4,5,6,7,8,9]

  3. 1 1 1 个元素出现 y + 1 y + 1 y+1 次,其余 x − 1 x - 1 x1 个元素各出现 y y y 次。例如 [ 5 , 5 , 6 , 6 , 7 , 7 , 8 , 8 , 8 ] [5,5,6,6,7,7,8,8,8] [5,5,6,6,7,7,8,8,8]

  4. 1 1 1 个元素出现 1 1 1 次,其余 x x x 个元素各出现 y y y 次。例如 [ 1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 8 ] [1,1,2,2,3,3,4,4,8] [1,1,2,2,3,3,4,4,8]

m > 1 m > 1 m>1 时,上述列举的情况都适用。当 m = 1 m = 1 m=1 时,符合情况 1 和情况 2。因此对于任意长度的前缀,都可以根据上述 4 4 4 种情况判断前缀是否符合要求。

根据上述分析可知,判断一个前缀是否合法时,需要计算前缀中的不同元素个数、每个元素的出现频率以及每个频率的出现次数,因此需要使用两个哈希表分别记录每个元素的出现频率和每个频率的出现次数,这两个哈希表分别为元素频率哈希表和频率次数哈希表。

从左到右遍历数组 nums \textit{nums} nums,对于 0 ≤ i < n 0 \le i < n 0i<n,当遍历到下标 i i i 时,前缀的长度是 i + 1 i + 1 i+1,令 num = nums [ i ] \textit{num} = \textit{nums}[i] num=nums[i],则 num \textit{num} num 的频率加 1 1 1。由于在遍历过程中,元素频率只会增加,不会减少,因此需要维护最大频率 maxFreq \textit{maxFreq} maxFreq,即所有元素的出现频率中的最大值,便于判断前缀是否符合要求。具体操作如下。

  1. 从元素频率哈希表中得到 num \textit{num} num 的频率 prevFreq \textit{prevFreq} prevFreq(如果 num \textit{num} num 不在元素频率哈希表中则 prevFreq = 0 \textit{prevFreq} = 0 prevFreq=0),令 freq = prevFreq + 1 \textit{freq} = \textit{prevFreq} + 1 freq=prevFreq+1,则 prevFreq \textit{prevFreq} prevFreq freq \textit{freq} freq 分别是 num \textit{num} num 之前的频率和最新的频率。

  2. 在元素频率哈希表中将 num \textit{num} num 的频率更新为 freq \textit{freq} freq,并用 freq \textit{freq} freq 更新 maxFreq \textit{maxFreq} maxFreq

  3. 在频率次数哈希表中将 prevFreq \textit{prevFreq} prevFreq 的次数减 1 1 1,将 freq \textit{freq} freq 的次数加 1 1 1。如果 prevFreq = 0 \textit{prevFreq} = 0 prevFreq=0,则不更新 prevFreq \textit{prevFreq} prevFreq 的次数。

  4. 更新两个哈希表之后,元素频率哈希表中的元素(关键字)个数即为不同元素个数,根据不同元素个数和频率次数哈希表判断当前的前缀是否符合要求,即是否符合上述 4 4 4 种情况中的至少 1 1 1 种。

    1. 如果不同元素个数等于 1 1 1,则符合情况 1。

    2. 如果不同元素个数等于 i + 1 i + 1 i+1,则符合情况 2。

    3. 如果频率次数哈希表中,频率 maxFreq \textit{maxFreq} maxFreq 的次数等于 1 1 1 且频率 maxFreq − 1 \textit{maxFreq} - 1 maxFreq1 的次数等于不同元素个数减 1 1 1,则符合情况 3。

    4. 如果频率次数哈希表中,频率 maxFreq \textit{maxFreq} maxFreq 的次数等于不同元素个数减 1 1 1 且频率 1 1 1 的次数等于 1 1 1,则符合情况 4。

  5. 如果当前的前缀符合要求,则将最长前缀的长度更新为 i + 1 i + 1 i+1(因为当前的前缀长度一定大于之前遍历到的任意前缀的长度),否则不更新最长前缀的长度。

上述操作中,对于每个下标 i i i,更新哈希表和判断当前的前缀是否符合要求的时间都是 O ( 1 ) O(1) O(1),因此总时间复杂度是 O ( n ) O(n) O(n)

代码

class Solution {
    public int maxEqualFreq(int[] nums) {
        Map<Integer, Integer> numFreqMap = new HashMap<Integer, Integer>();
        Map<Integer, Integer> freqCountMap = new HashMap<Integer, Integer>();
        int maxPrefixLength = 0;
        int maxFreq = 0;
        int length = nums.length;
        for (int i = 0; i < length; i++) {
            int num = nums[i];
            int prevFreq = numFreqMap.getOrDefault(num, 0);
            int freq = prevFreq + 1;
            numFreqMap.put(num, freq);
            maxFreq = Math.max(maxFreq, freq);
            if (prevFreq > 0) {
                freqCountMap.put(prevFreq, freqCountMap.get(prevFreq) - 1);
                if (freqCountMap.get(prevFreq) == 0) {
                    freqCountMap.remove(prevFreq);
                }
            }
            freqCountMap.put(freq, freqCountMap.getOrDefault(freq, 0) + 1);
            boolean flag = false;
            int distinctNums = numFreqMap.size();
            if (distinctNums == 1) {
                flag = true;
            } else if (distinctNums == i + 1) {
                flag = true;
            } else if (freqCountMap.get(maxFreq) == 1 && freqCountMap.getOrDefault(maxFreq - 1, 0) == distinctNums - 1) {
                flag = true;
            } else if (freqCountMap.get(maxFreq) == distinctNums - 1 && freqCountMap.getOrDefault(1, 0) == 1) {
                flag = true;
            }
            if (flag) {
                maxPrefixLength = i + 1;
            }
        }
        return maxPrefixLength;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums,对于每个元素,更新哈希表和判断当前的前缀是否符合要求的时间都是 O ( 1 ) O(1) O(1),因此总时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要使用两个哈希表分别记录每个元素的出现频率和每个频率的出现次数,两个哈希表的空间复杂度都是 O ( n ) O(n) O(n)

你可能感兴趣的:(数据结构和算法,#,哈希表,哈希表)