这是我最近面临的一个面试问题。程序返回数组中最大数字的索引[注意:数组可能包含也可能不包含多个最大数字副本],这样每个索引(包含最大数字)的概率为1 /最大数字被退回。
示例:
首先,我给出了O(n)时间和O(n)空间算法,我收集了最大索引集,然后从集合中返回一个随机数。但他要求O(n)时间和O(1)复杂度计划,然后我想出了这个。
int find_maxIndex(vector<int> a)
{
max = a[0];
max_index = 0;
count = 0;
for(i = 1 to a.size())
{
if(max < a[i])
{
max = a[i];
count = 0;
}
if(max == a[i])
{
count++;
if(rand < 1/count) //rand = a random number in the range of [0,1]
max_index = i;
}
}
return max_index;
}
我给了他这个解决方案。但我怀疑的是,这个程序是否会以相同的概率选择最大数的索引之一。希望我很清楚。有没有其他方法可以做到这一点?
答案 0 :(得分:3)
您的算法运行正常,您可以通过归纳证明它。
也就是说,假设它适用于任何大小为N
的数组,证明它适用于任何大小为N+1
的数组。
因此,给定一个大小为N+1
的数组,将其视为大小为N
的子数组,最后跟随一个新元素。通过假设,您的算法统一选择子数组中的一个最大元素......然后它的行为如下:
如果新元素大于子数组的最大值,则返回该元素。这显然是正确的。
如果新元素小于子数组的最大值,则返回子数组上算法的结果。也显然是正确的。
唯一稍微棘手的部分是当新元素等于子数组的max元素时。在这种情况下,让子数组中的最大元素数为k
。然后,根据假设,您的算法选择其中一个概率1/k
。通过保持概率为k/(k+1)
的相同元素,您可以根据需要选择相同元素的总概率等于1/k
* k /(k+1)
== 1/(k+1)
。你也选择了具有相同概率的最后一个元素,所以我们完成了。
要完成归纳证明,只需验证算法是否适用于大小为1的数组。另外,为了实现质量,请修复它不要在大小为零的数组上崩溃: - )
[更新]
顺便提一下,这个算法及其证明与Fisher-Yates shuffle密切相关(我一直认为是“Knuth的卡片改组算法”,但维基百科说我落后于时代)。
答案 1 :(得分:3)
你拥有的是Reservoir sampling!还有另一种易于理解的解决方案,但需要两次通过。
int find_maxIndex(vector<int> a){
int count = 1;
int maxElement = a[0];
for(int i = 1; i < a.size(); i++){
if(a[i] == maxElement){
count ++;
} else if(a[i] > maxElement){
count = 1;
maxElement = a[i];
}
}
int occurrence = rand() % count + 1;
int occur = 0;
for(int i = 0; i < a.size(); i++){
if(a[i] == maxElement){
occur++;
if(occur == occurrence) return i;
}
}
}
算法非常简单,首先查找第一遍中出现最大元素的次数。并选择一个随机事件并返回该事件的索引。虽然需要两次通过,但很容易理解。
答案 2 :(得分:1)
这个想法是合理的,但魔鬼在细节中。
首先,您使用的语言是什么?它可能会有所作为。来自C和C ++的rand()
将返回一个整数,除非返回1/count
,否则该整数不可能小于0
。即便如此,如果1/count
是整数除法,那么结果总是0
。
此外,您的计数结果为1.当您获得新的最大值时,它会以1
开头,但您会立即在下一个if
语句中将其递增。