问一道阿里的面试题如何求解

2020-02-17 10:17:08 +08:00
 dazhangpan

N 年前面试阿里的时候有一道算法题不会做,后来一直念念不忘,在网络搜了一下也没找到答案,至今仍然没想出来...

题目是给一个方法 foo(),能以各 50%的概率随机返回 0 或者 1。问题是基于 foo()方法,实现一个 boo()方法,该方法能以各 10%的概率返回[0-9]中的一个数字。

感觉对受过相关训练的同仁来说应该不难,无奈本人对算法不是太在行 o(╥﹏╥)o

8763 次点击
所在节点    程序员
67 条回复
ytmsdy
2020-02-17 11:55:00 +08:00
@stackexplode 二分不对的,永远都无法得到 10%这个概率。
yiqunz
2020-02-17 12:19:51 +08:00
@gaobing 用 5 位来排列组合比 4 位平均调用频率小,
5 位的平均调用次数是 5/(30/32)≈5.33 ,4 位平均调用次数是 4/(10/16)=6.4 /doge

@zhaorunze @ytmsdy
foo()本身就是二分一种表现 #1 结果导向 #3 过程导向 其实一样的,
看他二分的取值区间和摒弃区间都是一样的
[0-9] 每个数都是 10%,不要应该关注 10%,而是 [0-9] 每个数概率均等,想要概率均等那么区间就要满足 2 的 n 次方
摒弃一部分数字不影响目标数字的概率均等问题,产生了浪费罢了,这是不是和二进制很像 /doge
zhw2590582
2020-02-17 12:32:43 +08:00
得到的经验就是,凡是看到 0 和 1 出现的题目,就要联想到二进制解法
suiterchik
2020-02-17 12:40:46 +08:00
背后的数学原理是舍弃采样(或者其他的蒙特卡洛方法也行),给你一个硬币,别说均匀分布,高斯分布都能给你采出来
momocraft
2020-02-17 12:41:02 +08:00
假想有一个区间从[0,1)开始

每掷一次 foo()为 0 则取左一半,为 1 则取右一半,直到这个区间已经是某个 [0.1*n, 0.1*(n+1)) 的子集时,返回 n

从计算的形状上看也是一种二分。

全用浮点数计算时一定在有限步内结束,在这一点来说比需要重掷的稳定,不过不是严格等概率
shilyx
2020-02-17 13:01:10 +08:00
function foo() {
return Math.random() < 0.5;
}

function foo2() {
for (;;) {
let n = 0;
for (let i = 0; i < 4; ++i) {
n *= 2;
if (foo()) {
n += 1;
}
}
if (n <= 9) {
return n;
}
}
}

!function foo2_test(times) {
let res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];

for (var i = 0; i < times; i++) {
res[foo2()] += 1;
}

for (let i = 0; i < res.length; ++i) {
console.log(times + "次调用中" + i + "的次数: " + res[i]);
}
} (10000);
st2523684
2020-02-17 13:54:13 +08:00
类似加权调度算法
Mohanson
2020-02-17 14:03:17 +08:00
硬币连续抛掷 32 次得到一个随机 u32 数字,然后取余 10 即可.
wsssss
2020-02-17 14:17:17 +08:00
//随机 return 0 到 3
foo0_3(){
return foo() + foo()*2;
}

//随机 return 0 到 15
foo0_15{
return foo0_3() + foo0_3()*4;
}

main(){
//随机给 a 赋值,范围是 0 到 15,超过 9,再重新给 a 赋值;
int a = foo0_15;
while( 9 < a ){
a = foo0_15;
}
return a;
}
myd
2020-02-17 14:19:30 +08:00
@Mohanson 这么说也能换成 u16 或 u8 ?
wsssss
2020-02-17 14:20:49 +08:00
//随机 return 0 到 3
foo0_3 () {
return foo() + foo() * 2;
}

//随机 return 0 到 15
foo0_15 () {
return foo0_3() + foo0_3() * 4;
}

main () {
//随机给 a 赋值,范围是 0 到 15,超过 9,再重新给 a 赋值;
int a = foo0_15;
while ( 9 < a ) {
a = foo0_15();
}
return a;
}
lxy42
2020-02-17 14:21:24 +08:00
leetcode 有一道类似的题目, 使用 rand7 实现 rand10: https://leetcode.com/problems/implement-rand10-using-rand7/

解决方法是拒绝-接受采样: https://en.wikipedia.org/wiki/Rejection_sampling
Mohanson
2020-02-17 14:24:36 +08:00
@myd u8 太小了,0-5 数字出现几率会比其他会多一点
hitmanx
2020-02-17 14:27:46 +08:00
@Mohanson u32 和 u8 的问题依然一样啊,只是更趋近于 10%而已,依然不是严格意义上的 10%
mahone3297
2020-02-17 15:18:11 +08:00
@gaobing
@myd
这种舍弃 6 种,不返回,重新计算 的做法,从数学上讲,是否符合概率?最后得出的,真是是 10% 么?
wutiantong
2020-02-17 15:37:34 +08:00
1 楼(&2 楼)的解法是正确且本质的,这里分享一些额外的思考:

1. 不可能存在一个有限步的解法(证明略)
2. 不一定要每组 4 次,也可以每组 5 次,每组 6 次,,,
3. 如果我们关注调用 foo() 方法的次数,那么每组 4 次的期望值是 6.4 次,而每组 5 次的期望值是 5.33333333 次
4. 每组 5 次的方案是调用 foo()方法次数(期望值)最少的方案
buxudashi
2020-02-17 16:11:57 +08:00
思考了一下,给个最优解吧。
有十个位置,每个位置被 0 或 1 占位。于是就有了 0000000000-1111111111 (即 0-9 共 10 个位置)得到一个数字 x
这个数字每右移 1 位,只要大于 0,接着右移,而右移的次数称为 k
这个 k 就是 0-9 中的一个数字。所以 foo( )随机取 10 次,这 10 次组成的 x 所取的 k,就是答案。
2 楼给的答案有个极端最差,就是无穷次运行 foo 都会在后面 6 个数字里,取不到数字的。
epicnoob
2020-02-17 16:26:53 +08:00
@buxudashi 明显不对,k=0 的概率是 1/1024
buxudashi
2020-02-17 16:31:30 +08:00
@epicnoob k 为 0,再右移,右移后 k+1 ;

这个 K 就是 x 最高位所在的位置。当位置在第 0 位时,经过上一次操作,k 变成 1.
答案是 k-1 呀,就是 0.
因为是右移,它就不是 1/1024,而是 1/10,因为一共才移 10 次
buxudashi
2020-02-17 16:37:56 +08:00
10 个位置,这题就变向的变成 了:
最高位为 1 为位置 t 的地方。t 为 0-9 中的某一个数字。求 t

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/645168

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX