面试题:关于概率
Posted
技术标签:
【中文标题】面试题:关于概率【英文标题】:An interview question: About Probability 【发布时间】:2011-06-30 10:24:15 【问题描述】:面试题:
给定一个函数 f(x),1/4 次返回 0,3/4 次返回 1。 用 f(x) 写一个函数 g(x),1/2 次返回 0,1/2 次返回 1。
我的实现是:
function g(x) =
if (f(x) == 0) // 1/4
var s = f(x)
if( s == 1) // 3/4 * 1/4
return s // 3/16
else
g(x)
else // 3/4
var k = f(x)
if( k == 0) // 1/4 * 3/4
return k // 3/16
else
g(x)
我说的对吗?你的解决方案是什么?(你可以使用任何语言)
【问题讨论】:
返回 0 / 1 还是打印 0 / 1? 返回。很抱歉造成混乱。 你的函数可能会陷入无限循环 @Dave,可以,但不太可能。 ;px
的参数是干什么用的?好像没什么用。
【参考方案1】:
btilly 的答案中使用的相同方法的改进,每个g()
结果平均达到约 1.85 次对f()
的调用(下面记录的进一步改进达到约 1.75,tbilly 的约 2.6,Jim Lewis 接受的答案约 5.33)。代码在答案中显示较低。
基本上,我以偶数概率生成 0 到 3 范围内的随机整数:然后调用者可以测试位 0 的第一个 50/50 值,然后测试位 1 的第二个。原因:f()
1/4 和 3/4 的概率比一半更清晰地映射到四分之一。
算法说明
btilly 解释了算法,但我也会以我自己的方式这样做......
该算法基本上会生成一个介于 0 和 1 之间的随机实数数字x
,然后根据该数字落入哪个“结果桶”返回结果:
result bucket result
x < 0.25 0
0.25 <= x < 0.5 1
0.5 <= x < 0.75 2
0.75 <= x 3
但是,仅给定f()
生成一个随机实数是很困难的。我们必须首先知道我们的x
值应该在 0..1 范围内——我们将其称为初始“可能 x”空间。然后我们确定x
的实际值:
f()
:
如果 f()
返回 0(概率为 1 比 4),我们认为 x
位于“可能的 x”空间的下四分之一,并从该空间中消除上四分之三
如果 f()
返回 1(4 分之三的概率),我们认为 x
位于“可能 x”空间的上四分之三,并从该空间中消除下四分之一
当“可能的 x”空间完全包含在单个结果存储桶中时,这意味着我们已将 x
缩小到我们知道它应该映射到哪个结果值并且无需获取更多x
的具体值。
考虑这个图表可能有帮助,也可能没有帮助:-):
"result bucket" cut-offs 0,.25,.5,.75,1
0=========0.25=========0.5==========0.75=========1 "possible x" 0..1
| | . . | f() chooses x < vs >= 0.25
| result 0 |------0.4375-------------+----------| "possible x" .25..1
| | result 1| . . | f() chooses x < vs >= 0.4375
| | | . ~0.58 . | "possible x" .4375..1
| | | . | . | f() chooses < vs >= ~.58
| | ||. | | . | 4 distinct "possible x" ranges
代码
int g() // return 0, 1, 2, or 3
if (f() == 0) return 0;
if (f() == 0) return 1;
double low = 0.25 + 0.25 * (1.0 - 0.25);
double high = 1.0;
while (true)
double cutoff = low + 0.25 * (high - low);
if (f() == 0)
high = cutoff;
else
low = cutoff;
if (high < 0.50) return 1;
if (low >= 0.75) return 3;
if (low >= 0.50 && high < 0.75) return 2;
如果有帮助,中间人一次提供一个 50/50 结果:
int h()
static int i;
if (!i)
int x = g();
i = x | 4;
return x & 1;
else
int x = i & 2;
i = 0;
return x ? 1 : 0;
注意:这可以通过让算法从考虑 f()==0 结果切换到在下四分之一上磨练,改为在上四分之一上磨练来进行进一步调整,基于此平均解决更快地到达结果桶。从表面上看,这在第三次调用 f() 时似乎很有用,因为上半部分的结果表明立即结果为 3,而下半部分的结果仍然跨越概率点 0.5,因此结果为 1 和 2。当我尝试它时,结果实际上更糟。需要进行更复杂的调整才能看到实际的好处,我最终为 g() 的第二次到第十一次调用编写了下截止与上截止的蛮力比较。我发现的最佳结果是平均约 1.75,这是由于对 g() 的第 1 次、第 2 次、第 5 次和第 8 次调用寻求低(即设置low = cutoff
)。
【讨论】:
【参考方案2】:这是一个基于中心极限定理的解决方案,最初是由于我的一个朋友:
/*
Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1. Write a function g(x) using f(x) that 1/2 times returns 0, 1/2 times returns 1.
*/
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstdio>
using namespace std;
int f()
if (rand() % 4 == 0) return 0;
return 1;
int main()
srand(time(0));
int cc = 0;
for (int k = 0; k < 1000; k++) //number of different runs
int c = 0;
int limit = 10000; //the bigger the limit, the more we will approach %50 percent
for (int i=0; i<limit; ++i) c+= f();
cc += c < limit*0.75 ? 0 : 1; // c will be 0, with probability %50
printf("%d\n",cc); //cc is gonna be around 500
return 0;
【讨论】:
【参考方案3】:这很像蒙蒂霍尔悖论。
一般。
Public Class Form1
'the general case
'
'twiceThis = 2 is 1 in four chance of 0
'twiceThis = 3 is 1 in six chance of 0
'
'twiceThis = x is 1 in 2x chance of 0
Const twiceThis As Integer = 7
Const numOf As Integer = twiceThis * 2
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Const tries As Integer = 1000
y = New List(Of Integer)
Dim ct0 As Integer = 0
Dim ct1 As Integer = 0
Debug.WriteLine("")
''show all possible values of fx
'For x As Integer = 1 To numOf
' Debug.WriteLine(fx)
'Next
'test that gx returns 50% 0's and 50% 1's
Dim stpw As New Stopwatch
stpw.Start()
For x As Integer = 1 To tries
Dim g_x As Integer = gx()
'Debug.WriteLine(g_x.ToString) 'used to verify that gx returns 0 or 1 randomly
If g_x = 0 Then ct0 += 1 Else ct1 += 1
Next
stpw.Stop()
'the results
Debug.WriteLine((ct0 / tries).ToString("p1"))
Debug.WriteLine((ct1 / tries).ToString("p1"))
Debug.WriteLine((stpw.ElapsedTicks / tries).ToString("n0"))
End Sub
Dim prng As New Random
Dim y As New List(Of Integer)
Private Function fx() As Integer
'1 in numOf chance of zero being returned
If y.Count = 0 Then
'reload y
y.Add(0) 'fx has only one zero value
Do
y.Add(1) 'the rest are ones
Loop While y.Count < numOf
End If
'return a random value
Dim idx As Integer = prng.Next(y.Count)
Dim rv As Integer = y(idx)
y.RemoveAt(idx) 'remove the value selected
Return rv
End Function
Private Function gx() As Integer
'a function g(x) using f(x) that 50% of the time returns 0
' that 50% of the time returns 1
Dim rv As Integer = 0
For x As Integer = 1 To twiceThis
fx()
Next
For x As Integer = 1 To twiceThis
rv += fx()
Next
If rv = twiceThis Then Return 1 Else Return 0
End Function
End Class
【讨论】:
【参考方案4】:假设
P(f[x] == 0) = 1/4
P(f[x] == 1) = 3/4
并且需要具有以下假设的函数g[x]
P(g[x] == 0) = 1/2
P(g[x] == 1) = 1/2
我相信g[x]
的以下定义就足够了(Mathematica)
g[x_] := If[f[x] + f[x + 1] == 1, 1, 0]
或者,或者在 C 中
int g(int x)
return f(x) + f(x+1) == 1
? 1
: 0;
这是基于f[x], f[x+1]
的调用会产生以下结果的想法
0, 0,
0, 1,
1, 0,
1, 1
总结我们的每个结果
0,
1,
1,
2
其中 1 的总和表示可能的总和结果的 1/2,任何其他总和构成其他 1/2。
编辑。 正如 bdk 所说 - 0,0 的可能性低于 1,1 因为
1/4 * 1/4 < 3/4 * 3/4
但是,我自己很困惑,因为给定 f[x]
(Mathematica) 的以下定义
f[x_] := Mod[x, 4] > 0 /. False -> 0, True -> 1
或者在 C 中
int f(int x)
return (x % 4) > 0
? 1
: 0;
那么执行f[x]
和g[x]
得到的结果似乎有预期的分布。
Table[f[x], x, 0, 20]
0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0
Table[g[x], x, 0, 20]
1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1
【讨论】:
我认为这行不通。它假定 f(x)+f(x) 的四个可能值的概率相同。实际上,0,0 的可能性远小于 1,1 看来你自己搞糊涂了:-)。这些函数并不是真正的 f(x) 和 g(x)……它们没有输入,只是 f() 和 g()。因此,没有 f(x+1)。至于 f() + f()... 有 1/16 的机会是 0、6/16 的 1 和 9/16 的 2。你的 g() 函数在测试中“切换”为 1,所以也会这两个结果有 6/16 对 10/16 的机会(需要同样的可能性)。【参考方案5】:由于 f() 的每次返回都代表 3/4 的机会为 TRUE,因此通过一些代数我们可以适当地平衡赔率。我们想要的是另一个函数 x(),它返回 TRUE 的平衡概率,所以
function g()
return f() && x();
50% 的时间返回 true。
所以让我们求出 x (p(x)) 的概率,给定 p(f) 和我们想要的总概率 (1/2):
p(f) * p(x) = 1/2
3/4 * p(x) = 1/2
p(x) = (1/2) / 3/4
p(x) = 2/3
所以 x() 应该以 2/3 的概率返回 TRUE,因为 2/3 * 3/4 = 6/12 = 1/2;
因此以下应该适用于 g():
function g()
return f() && (rand() < 2/3);
【讨论】:
我认为这暗示你只能使用f(x)
作为随机生成器,而不能使用任何其他rand()
。
啊,有道理。谢谢!【参考方案6】:
您的解决方案是正确的,但效率较低且逻辑重复较多。这是相同算法的 Python 实现,形式更简洁。
def g ():
while True:
a = f()
if a != f():
return a
如果 f() 很昂贵,您可能希望更复杂地使用匹配/不匹配信息来尝试以更少的调用返回。这是最有效的解决方案。
def g ():
lower = 0.0
upper = 1.0
while True:
if 0.5 < lower:
return 1
elif upper < 0.5:
return 0
else:
middle = 0.25 * lower + 0.75 * upper
if 0 == f():
lower = middle
else:
upper = middle
这平均需要大约 2.6 次调用 g()
。
它的工作方式是这样的。我们试图从 0 到 1 中选择一个随机数,但是一旦我们知道该数字是 0 还是 1,我们就会停止。我们开始知道该数字在区间 (0, 1) 内。 3/4 的数字在区间的底部 3/4,1/4 的数字在区间的顶部 1/4。我们根据对f(x)
的调用来决定哪个。这意味着我们现在处于更小的区间。
如果我们清洗、漂洗和重复足够多次,我们可以尽可能精确地确定我们的有限数,并且在原始区间的任何区域结束的概率绝对相等。尤其是我们有一个均匀的概率清盘大于或小于 0.5。
如果你愿意,你可以重复这个想法,逐个生成无穷无尽的比特流。事实上,这可以证明是生成这种流的最有效方式,并且是信息论中熵思想的来源。
【讨论】:
哎呀,你是对的。固定的。它的工作方式是,如果我们在一个区间内没有停下来,我们就会在我们的包上统一选择一个从 0 到 1 的数字。实际上,一旦我们知道 0.5 的哪一边,我们就会停下来号码将亮起。我会尝试添加解释。f()
的熵是lg(4)/4+lg(4/3)*3/4 ≈ 0.81
。因此,1.23
对f()
的调用似乎就足够了?【参考方案7】:
您的算法的问题在于它以高概率重复自身。我的代码:
function g(x) =
var s = f(x) + f(x) + f(x);
// s = 0, probability: 1/64
// s = 1, probability: 9/64
// s = 2, probability: 27/64
// s = 3, probability: 27/64
if (s == 2) return 0;
if (s == 3) return 1;
return g(x); // probability to go into recursion = 10/64, with only 1 additional f(x) calculation
我测量了 f(x)
为您的算法和我的算法计算的平均次数。对于您的f(x)
,每g(x)
计算大约计算5.3 次。使用我的算法,这个数字减少到 3.5 左右。到目前为止,其他答案也是如此,因为它们实际上与您所说的算法相同。
P.S.:您的定义目前没有提到“随机”,但可能是假设的。请参阅我的其他答案。
【讨论】:
我投了这个票,然后取消了,因为我以为我看到了一个错误,但现在不能再投了。 :-( 无论如何,+1!当你编辑时,我相信我可以再次投票。也许再解释一下答案?:) @Steven,我看到你玩弄我的名声 :) 您可以通过区分 0、0、1 和 1、0、0.... 来处理 10 个未处理案例中的另外 6 个。【参考方案8】:如前所述,您对概率的定义不是很好。通常这意味着不仅概率很好,而且distribution
也很好。否则,您可以简单地编写 g(x) 它将返回 1,0,1,0,1,0,1,0 - 它会返回 50/50,但数字不会是随机的。
另一种作弊方法可能是:
var invert = false;
function g(x)
invert = !invert;
if (invert) return 1-f(x);
return f(x);
此解决方案将比所有其他解决方案更好,因为它只调用一次f(x)
。但结果不会很随机。
【讨论】:
我不认为这是作弊——你给了面试官他们要求的东西——话虽如此,你的函数最终可能会返回 (0) 反转为 (1), (1) => (1), (1) 反转为 (0) , (1) => (1), 3 个 1 和 1 0。为什么不只计算 f() 一次(说你已经使用它)然后只每次调用 g() 时翻转结果。【参考方案9】:Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1
从字面上理解这个语句,如果 f(x) 被调用四次,将总是返回 0 一次和 1 3 次。这不同于说 f(x) 是一个概率函数,并且 0 比 1 的比率在多次迭代中将接近 1 比 3(1/4 对 3/4)。如果第一个解释是有效的,那么无论从序列中的哪个位置开始,f(x) 的唯一有效函数都是重复序列 0111。 (或 1011 或 1101 或 1110,它们是来自不同起点的相同序列)。鉴于该约束,
g()= (f() == f())
应该足够了。
【讨论】:
我已经多次看到这个问题的变体,以至于我已经知道答案,甚至不必计算概率。因此,在面试问题的背景下,我认为“独立、相同分布的试验”是正确的假设。当然,有些面试官很刻薄,所以最好知道另一个需要准备的“技巧问题”变体。【参考方案10】:如果连续调用 f(x) 两次,可能会出现以下结果(假设 对 f(x) 的连续调用是独立的、同分布的试验):
00 (probability 1/4 * 1/4)
01 (probability 1/4 * 3/4)
10 (probability 3/4 * 1/4)
11 (probability 3/4 * 3/4)
01 和 10 出现的概率相同。所以迭代直到你得到其中之一 情况,然后适当地返回 0 或 1:
do
a=f(x); b=f(x);
while (a == b);
return a;
每次迭代只调用一次 f(x) 并跟踪这两个可能很诱人 最新的值,但这不起作用。假设第一个滚动是 1, 概率为 3/4。您将循环直到第一个 0,然后返回 1(概率为 3/4)。
【讨论】:
嗯,贝叶斯定理的有趣用法,其中循环引入了归一化…… 谢谢,这与“用有偏见的硬币制作公平的硬币”经典问题***.com/questions/5429045/… 中的答案相同以上是关于面试题:关于概率的主要内容,如果未能解决你的问题,请参考以下文章