• 519. Random Flip Matrix(Fisher-Yates洗牌算法)


    1. 问题

    给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零。

    2. 思路

    拒绝采样
    (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n-1]相当于矩阵下标对应的一维索引。
    (2)用一个arrays数组存放矩阵元素为1的索引。每次从 [0, n-1]取数,这个数可以表示矩阵元素的索引,如果取的数已经在这个数组里,说明这个索引对应的矩阵元素已经被flip为1了,则放弃,继续取数,直到取到的数不在数组里(对应的矩阵元素为0),就把取到的数加入arrays数组,表示已经被flip为1。
    (3)取到的这个数(矩阵元素对应的一维索引)可以转换成矩阵的位置下标然后返回。
    (4)关于拒绝采样的原理,我在470. Implement Rand10() Using Rand7() (拒绝采样Reject Sampling)做过阐述。简单说就是,当矩阵中含有0和1时,我想在只含有0的这些数中取样,但是这样取样比较困难,我可以直接从整个矩阵采样,当采样到1的时候拒绝,继续采样,直到采样到0为止,这样子的采样可以视为“在只含有0的这些数中取样”的一种近似,特别是1的个数远小于0的个数时,两种采样会很接近。
    (5)缺点:但是当1的个数很大时,这样采样的效率就很低了,可能大部分情况采样的都是1,需要采样很多次才能采样到0。note中提到,行和列最大为1万,那么总大小就为一亿,而flip和reset的调用次数不会超过1000,只看这个最坏情况的话,这么做还挺ok的,毕竟flip这么少次,1的个数相对于0来说可以忽略不计了。

    (方法二)Fisher-Yates Shuffle算法
    Fisher-Yates洗牌算法是用来打乱一个随机序列的算法,主要步骤为:在0到n(索引)之间生成一个数m,交换m和n(索引对应的数),n(索引)减掉1,循环这三步,直到n等于0。主要思想就是每次采样(索引)时,当前随机采样到的数(索引对应的数)交换到最后一个数(末尾索引对应的数),然后采样池数量减一(末尾索引减一),然后继续采样和交换(不断迭代),直到采样池为空。这里参考了Fisher-Yates洗牌算法,主要步骤如下:

    (1)建立一维数组,每个元素含有三个值,两个值为矩阵坐标,一个为flip值,0/1。
    (2)每次flip时,按数组长度,随机抽取一个索引值,将这个索引对应的元素里的flip值从0变成1,然后把这个元素和最后一个元素交换。
    (3)数组长度减1,然后返回最后一个元素(这个“最后一个元素”指的是数组长度减1之前,数组的最后一个元素)。
    (4)对于reset操作,只需要把所有flip值改为0即可。
    (5)然而这样做超时了,因为要建立的这个一维数组太大了,note中说到,行和列最大为1万,那么总大小就为一亿。而且flip和reset的调用次数不会超过1000,所以很多元素是不会被采样到的,用这么大的数组来维护实在是太奢侈了。实际上我们只要维护那些已采样的元素就可以了。
    (6)考虑不使用这么大的数组,而是使用一个dict来映射已采样元素和最后一个元素,下面的方法三阐述了这样的做法。

    (方法三)Fisher-Yates Shuffle算法,利用dict字典映射
    (1)计算矩阵的元素个数记作n,那么[0, n-1]相当于矩阵下标对应的一维索引。
    (2)每次采样(flip)时,维护一个字典dict,key表示采样到的索引,value表示这一次采样时的末尾索引。每次采样时末尾索引都不一样,第一次采样时末尾索引为n-1,每次采样前,我们都把末尾索引减1。
    (3)这样,如果采样到“已经采样过的索引”,我们就可以从dict中根据“已经采样过的索引”(key)得到value(“采样这个已经采样过的索引时记录下来的末尾索引”)来作为我们的采样索引。
    (4)如果采样到“还未采样过的索引”,则直接使用这个索引来作为我们的采样索引。
    (5)在返回采样索引之前,我们需要更新dict,插入(更新)采样索引对应的value,即这次采样时的末尾索引(因为末尾索引可能也被采样过,所以要先从dict中查看是否存在“末尾索引”,如果采样则使用末尾索引对应的value)。
    (6)最后返回采样索引对应的矩阵位置

    3. 代码

    拒绝采样

    import random
    class Solution(object):
        def __init__(self, n_rows, n_cols):
            self.n_rows, self.n_cols = n_rows, n_cols
            self.n = n_rows * n_cols
            self.arrays = []
    
        def flip(self):
            while True:
                i = random.randint(0,self.n-1)
                if i not in self.arrays:
                    self.arrays.append(i)
                    break
            return [i/self.n_cols, i%self.n_cols]
            
        def reset(self):
            self.arrays = []
    

    时间复杂度O(1),最坏情况O(无穷)
    空间复杂度O(n)

    (方法三)Fisher-Yates Shuffle算法,利用dict字典映射

    import random
    class Solution(object):
        def __init__(self, n_rows, n_cols):
            self.n_rows, self.n_cols = n_rows, n_cols
            self.reset()
    
        def flip(self):
            self.n -= 1
            i = random.randint(0, self.n)
            index = self.dic.get(i, i)
            self.dic[i] = self.dic.get(self.n, self.n)
            return [index / self.n_cols, index % self.n_cols]
    
        def reset(self):
            self.n = self.n_rows * self.n_cols
            self.dic = {}
    

    时间复杂度O(1)
    空间复杂度O(n)

    4. 类似题目

    470. Implement Rand10() Using Rand7() (拒绝采样Reject Sampling)
    478. Generate Random Point in a Circle

  • 相关阅读:
    【树状数组】bzoj2743 [HEOI2012]采花
    【二分答案】bzoj1639 [Usaco2007 Mar]Monthly Expense 月度开支
    【二分答案】【最短路】bzoj1614 [Usaco2007 Jan]Telephone Lines架设电话线
    【二分答案】【Heap-Dijkstra】bzoj2709 [Violet 1]迷宫花园
    【二分答案】【字符串哈希】bzoj2084 [Poi2010]Antisymmetry
    【二分答案】【最大流】bzoj1305 [CQOI2009]dance跳舞
    【计算几何】【二分答案】【最大流】bzoj1822 [JSOI2010]Frozen Nova 冷冻波
    【二分答案】【最大流】bzoj3130 [Sdoi2013]费用流
    【动态规划】bzoj3992 [Sdoi2015]序列统计 10分
    【二分答案】【最大流】bzoj3993 [Sdoi2015]星际战争
  • 原文地址:https://www.cnblogs.com/liaohuiqiang/p/9868908.html
Copyright © 2020-2023  润新知