操作数组, 使得其中相同的元素的距离>=k

2021-12-23 11:50:02 +08:00
 icySoda

最近业务上的一个需求. 真实场景是这样, 0-31 一共 32 个数, 给定长度 n, 返回一个长度为 n 的随机数组. 使得:

  1. 以下元素出现的概率为 50%, 0,7,8,15,16,23,24,31
  2. 以下元素出现的概率为 45%: 1~6,25~30, 9,14,17,22
  3. 其他元素出现的概率为 5%: 10~14, 18~21
  4. 数组中相同元素的位置距离>=6

前 3 点很容易满足, 第 4 点没什么思路, 请老师们指教

1542 次点击
所在节点    算法
28 条回复
Mutoo
2021-12-23 12:26:51 +08:00
1 号桶 10 个;
2 号桶 9 个;
3 号桶 1 个;
每个桶对应一个序列,序列里是上面所指定的数字;
初始化的时候将三个序列洗牌;
每回合随机取一个桶,获得相应序列中的首个数字,输出,并将数字放回到序列末尾;
每个序列被取 6 ( k )次后重新洗牌;
lysS
2021-12-23 12:40:41 +08:00
先按前三个要求写一个 rand, rand 每次返回一个值,检查这个值是否符合条件 4 ,不符合重新生成,符合就 append
icySoda
2021-12-23 13:07:47 +08:00
@Mutoo 这个并不能保证 4. 前一个回合输出的最后数字是 x, 下一个回合第一个输出的数字也可能是 x.
@lysS 最后一个值运气不好的话, 可能 cpu 冒烟也不一定能得到结果
Mutoo
2021-12-23 13:37:50 +08:00
洗牌只发生在初始化和每 k 次 /序列。取的时候只取序列首个数,FIFO 。只要你的序列长度大于 k 就可以保证 4 。
Mutoo
2021-12-23 13:39:20 +08:00
更正一下:每号桶对应一个序列,不是每个桶对应一个序列。总的就三个序列。
ipwx
2021-12-23 13:46:30 +08:00
@Mutoo 不用放到末尾,加个 next 指针就行。洗牌后,每个桶内每次取 next 指针的数字就行。当 next 指针用完整个桶在重新洗牌。。。

@icySoda 不过这其实和原问题的分布略有不同,但是我觉得足够相近了。这套重新洗牌是最快的实现方案,不然你就得维护过去六个元素的集合,如果下一个采样到的元素属于这个集合就重新采样。。。不过大概也不会太慢。

每个点上重新采样一次的概率是 6/32 ,大概 1/5 。期望采样 2 次就能拿到一个合适的数字。倒也不是不行
ipwx
2021-12-23 13:52:05 +08:00
Mutoo
2021-12-23 14:11:34 +08:00
写了个代码验证一下,好像确实不能保证 4 。这个规则只有首位数字能保证 k 次不重复。

想了一下,如果有个序列有 7 个数,要保正条件 4 ,生成的结果中每 6 个数都不重复的话,序列就无法随机了。
Maboroshii
2021-12-23 14:32:31 +08:00
最简单的想到,如果 n<=6 ,那么不允许出现重复数字
也就是这个结果和 n 的大小相关的,这个完全影响了随机出现

一个蠢办法,离线算出来出符合第 4 点的有限多的数组,最后从这些数组里面随机一个拿出来用就行了
Maboroshii
2021-12-23 14:41:44 +08:00
想到一个生成的办法

初始化一个空数组(长度大大于 n)
do {
随机一个数 a
判断这个数是否在数组中
if 存在数 a0 == a {
把这个数放在 a0 位置的 6 位以后 (如果数组长度不够,扩展数组长度)
} else {
放在第一个不为空的位置
}
} while 从数组开始能取到长度为 n 的元素都不为空的片段

这样可以搞一个长度为 n 的窗口,可以一直得到符合要求的数组
GuuJiang
2021-12-23 15:15:16 +08:00
每生成一个数,就在接下来的 6 次随机中从随机池中暂时移除,6 次以后再加回来
生成每个数时如果随机到了一个已被移除的数则重试
ipwx
2021-12-23 15:23:23 +08:00
@Maboroshii
@Mutoo
@GuuJiang

你看我 7L 的代码,还有 6L 的分析。从期望这只要多采样一倍就行。。。估计是最合适的方案了

加上计数器统计总共的采样次数:

https://ideone.com/e1Ek8y

可以知道这次实验,生成 100 个数字只需要采样 129 次,丢弃率只有不到 30%
icySoda
2021-12-23 15:23:35 +08:00
@Maboroshii 这样没法保证数组的前 n 个符合概率要求吧?
@ipwx 这是个办法, 就是概率有点出入, 不过我可以离线算几组概率和要求比较近的拿来用
ipwx
2021-12-23 15:24:16 +08:00
@icySoda 我的代码方法没有出入,完全按照楼主的要求来。
ipwx
2021-12-23 15:25:03 +08:00
GuuJiang
2021-12-23 15:27:21 +08:00
“移除”和“加回来”只是形象的说法,实际实现时只需要记录每个数字最后出现位置,随机结果中检查距离最后出现位置是否小于 6 ,如果是则重试
预判到可能会有人怀疑这样改变了概率,其实不会,根据规则定义,6 次之内出现过的数据概率本来就只能为 0 ,而其他的数仍然是原始的概率,如果不相信的话思考下下面这个简化的例子就明白了
只用一个 6 面骰子,如何产生 1-5 的均匀分布随机数,答案就是如果扔到 6 就不算,重新扔一次,通过条件概率可以很容易计算出这样操作产生的随机数就是 1-5 均匀分布,简单地说就是对于一个随机试验,抛弃某些特定的结果不会影响未被抛弃的那些结果的概率
icySoda
2021-12-23 15:27:42 +08:00
@ipwx 你的循环以"是否满足第四点"为跳出的条件, 会导致数量最多的第 2 点的数据概率偏大.
我离线算了几组, 第 2 点所列的元素概率大体都在 50%左右
Mutoo
2021-12-23 15:36:45 +08:00
重采样必然会影响概率。至于影响多少很难讲。
@ipwx
icySoda
2021-12-23 15:40:09 +08:00
@GuuJiang 我要消化一下😂
GuuJiang
2021-12-23 15:45:22 +08:00
@Mutoo 不会影响的,我给的方案也是同一个思路,用条件概率公式简单算一下就知道了

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

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

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

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

© 2021 V2EX