【问题标题】:Finding mode for every window of size k in an array查找数组中每个大小为 k 的窗口的模式
【发布时间】:2021-12-20 12:41:00
【问题描述】:

给定一个大小为 n 和 k 的数组,如何找到每个大小为 k 的连续子数组的模式?

例如

arr = 1 2 2 6 6 1 1 7 
k = 3 
ans = 2 2 6 6 1 1

我正在考虑有一个键为 no 且值为频率的哈希图,键为频率且值为数字的树图,并有一个队列以在大小 > k 时删除第一个元素。这里的时间复杂度是o(nlog(n))。我们可以在 O(1) 中做到这一点吗?

【问题讨论】:

  • 如果同一个窗口内有多个相同模式的元素怎么办?
  • 使用桶队列。
  • 我相信你可以在 O(n) 内完成。

标签: java arrays algorithm data-structures


【解决方案1】:

这可以在 O(n) 时间内完成

我对这个问题很感兴趣,部分原因是,正如我在 cmets 中指出的那样,我确信它可以在 O(n) 时间完成。上周末我有一些时间,所以我写了我对这个问题的解决方案。

方法:模式频率

基本概念是这样的:数字集合的模式是在该集合中以最高频率出现的数字。

这意味着每当您向集合中添加一个数字时,如果添加的数字还不是众数之一,那么众数的频率不会改变。所以对于集合(8 9 9),模式值是{9},模式频率是2。如果您将 5 添加到此集合 ((8 9 9 5)) 中,则模式频率和模式值都不会改变。相反,如果您将 8 添加到集合 ((8 9 9 8)) 中,则模式值将更改为 {9, 8},但模式频率仍保持在 2 不变。最后,如果您将 9 添加到集合 ((8 9 9 9)) 中,现在模式频率会增加 1。

因此,在所有情况下,当您向集合添加一个数字时,模式频率要么保持不变,要么仅增加一。同样,当您从集合中删除单个数字时,模式频率要么保持不变,要么最多下降一个。因此,对集合的所有增量更改只会导致两个可能的新模式频率。这意味着,如果我们将集合的所有不同数字按其频率索引,那么我们总能在恒定时间内找到新模式(即O(1))。

为了实现这一点,我使用了一个自定义数据结构(“ModeTracker”),它有一个multiset(“numFreqs”)来存储集合的不同数字以及它们在集合中的当前频率。这是用 Dictionary 实现的(我认为这是 Java 中的 Map)。因此,给定一个数字,我们可以使用它在 O(1) 中找到它在集合中的当前频率。

此数据结构还有一个集合数组(“freqNums”),给定特定频率将返回当前集合中具有该频率的所有数字。

我在下面包含了这个数据结构类的代码。并不是说这是在 C# 中实现的,因为我对 Java 的了解还不够,无法在那里实现它,但我相信 Java 程序员应该可以毫不费力地翻译它。

(伪)代码:

类模式跟踪器 { HashSet[] 频率数; //每个频率的数字 字典 numFreqs; //每个数字的频率 int modeFreq_ = 0; //当前模式的频率
    public ModeTracker(int maxFrequency)
    {
        freqNums = new HashSet<int>[maxFrequency + 2];
        // populate frequencies, so we dont have to check later
        for (int i=0; i<maxFrequency+1; i++)
        {
            freqNums[i] = new HashSet<int>();
        }

        numFreqs = new Dictionary<int, int>();
    }

    public int Mode { get { return freqNums[modeFreq_].First(); } }

    public void addNumber(int n)
    {
        int newFreq = adjustNumberCount(n, 1);

        // new mode-frequency is one greater or the same 
        if (freqNums[modeFreq_+1].Count > 0) modeFreq_++;
    }

    public void removeNumber(int n)
    {
        int newFreq = adjustNumberCount(n, -1);

        // new mode-frequency is the same or one less
        if (freqNums[modeFreq_].Count == 0)  modeFreq_--;
    }

    int adjustNumberCount(int num, int adjust)
    {
        // make sure we already have this number
        if (!numFreqs.ContainsKey(num))
        {
            // add entries for it
            numFreqs.Add(num, 0);
            freqNums[0].Add(num);
        }

        // now adjust this number's frequency
        int oldFreq = numFreqs[num];
        int newFreq = oldFreq + adjust;
        numFreqs[num] = newFreq;

        // remove old freq for this number and and the new one
        freqNums[oldFreq].Remove(num);
        freqNums[newFreq].Add(num);

        return newFreq;
    }
}

另外,下面是一个小 C# 函数,演示如何使用此数据结构来解决问题中最初提出的问题。

    int[] ModesOfSubarrays(int[] arr, int subLen)
    {
        ModeTracker tracker = new ModeTracker(subLen);
        int[] modes = new int[arr.Length - subLen + 1];

        for (int i=0; i < arr.Length; i++)
        {
            //add every number into the tracker
            tracker.addNumber(arr[i]);

            if (i >= subLen)
            {
                // remove the number that just rotated out of the window
                tracker.removeNumber(arr[i-subLen]);
            }

            if (i >= subLen - 1)
            {
                // add the new Mode to the output
                modes[i - subLen + 1] = tracker.Mode;
            }
        }

        return modes;
    }

我已经对此进行了测试,它似乎在我的所有测试中都能正常工作。

复杂性分析

浏览 `ModesOfSubarrays()` 函数的各个步骤:
  1. 在 O(n) 或更短的时间内创建新的 ModeTracker 对象。
  2. modes[] 数组在 O(n) 时间内创建。
  3. For(..) 循环 N 次: . 3a:addNumber() 函数需要 O(1) 时间 . 3b:removeNumber() 函数需要 O(1) 时间 . 3c:获取新模式需要 O(1) 时间

所以总时间是O(n) + O(n) + n*(O(1) + O(1) + O(1)) = O(n)

如果您对此代码有任何疑问,请告诉我。

【讨论】:

    猜你喜欢
    • 2019-11-14
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    • 2019-06-04
    相关资源
    最近更新 更多