这可以在 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()` 函数的各个步骤:
- 在 O(n) 或更短的时间内创建新的 ModeTracker 对象。
- modes[] 数组在 O(n) 时间内创建。
- 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)
如果您对此代码有任何疑问,请告诉我。