面试问题:
给定函数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 :(得分:59)
如果连续两次调用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)。
答案 1 :(得分:8)
您的解决方案是正确的,如果效率低下且逻辑更复杂。这是一个更简洁的同一算法的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。
如果你想要,你可以重复这个想法,逐一产生无穷无尽的比特流。事实上,这可以证明是生成这种流的最有效方式,也是信息理论中 entropy 理念的源泉。
答案 2 :(得分:8)
您的算法的问题在于它以高概率重复自身。我的代码:
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。:你的定义目前没有提及'随机',但可能是假设。看到我的其他答案。
答案 3 :(得分:3)
Given a function f(x) that 1/4 times returns 0, 3/4 times returns 1
从字面上理解这句话,f(x)如果被调用四次将总是返回零一次和1次3次。这与说f(x)是概率函数不同,并且0到1的比率在许多次迭代中将接近1到3(1/4对3/4)。如果第一种解释是有效的,那么满足条件的f(x)的唯一有效函数,无论您从哪个序列开始,都是序列0111重复。 (或1011或1101或1110,它们是来自不同起点的相同序列)。鉴于这种约束,
g()= (f() == f())
应该足够了。
答案 4 :(得分:3)
正如已经提到的,你的定义在概率方面并不是那么好。通常这意味着不仅概率良好,而且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)
一次。但结果不会很随意。
答案 5 :(得分:3)
对btilly答案中使用的相同方法进行了改进,平均每f()
个g()
调用约1.85次调用(下面记录的进一步细化达到~1.75,大约是2.6,Jim Lewis的接受答案〜5.33)。代码在答案中显得较低。
基本上,我在偶数概率下生成0到3范围内的随机整数:然后调用者可以测试第0位表示第一个50/50值,第1位计算第一个50/50值。原因:1/4和3/4的f()
概率比半部分更清晰地映射到四分之一。
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(概率为3的4),我们认为x
位于“可能的x”空间的上四分之三,并消除该空间的下四分之一< / LI>
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结果切换到较低的四分之一,以及在上四分之一处进行磨练来进一步调整,基于此平均结算更快地到达结果桶。从表面上看,当上四分之一结果表示立即结果为3时,这对f()的第三次调用似乎很有用,而较低四分之一的结果仍然跨越概率点0.5,因此结果为1和2.当我尝试时,结果实际上更糟。需要进行更复杂的调整才能看到实际的好处,最后我写了一个蛮力的比较,即对第二到第十一次调用g()的低截止值和高截止值。我发现的最好结果是平均值为~1.75,这是由于第一次,第二次,第五次和第八次调用g()寻求低位(即设置low = cutoff
)。
答案 6 :(得分:1)
这是一个基于中心极限定理的解决方案,最初是由于我的一位朋友:
/*
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;
}
答案 7 :(得分:0)
由于f()的每次返回表示TRUE的概率为3/4,因此使用某些代数我们可以恰当地平衡赔率。我们想要的是另一个函数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);
}
答案 8 :(得分:0)
假设
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}
答案 9 :(得分:0)
这很像蒙蒂霍尔悖论。
一般而言。
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