leetcode384. 打乱数组

传送门

题目:打乱一个没有重复元素的数组。

示例:
// 以数字集合 1, 2 和 3 初始化数组。
int[] nums = {1,2,3};
Solution solution = new Solution(nums);
// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。
solution.shuffle();
// 重设数组到它的初始状态[1,2,3]。
solution.reset();
// 随机返回数组[1,2,3]打乱后的结果。
solution.shuffle();

一、使用:随机乱置算法(洗牌算法)。

最常用的洗牌算法。因为该算法的细节容易出错,且存在好几种变体,虽有细微差异但都是正确的,
所以这里只介绍一种简单的。
此类算法都是靠随机选取元素交换来获取随机性,直接看代码(伪码)

得到一个在在左开右闭区间 [min, max) 内的随机整数

int randNum(int min, int max)

void shuffle(int[] arr) {
    int n = arr.length();
    for (int i = 0 ; i < n; i++) {
        int rand = randNum(i, n); // 从 i 到最后随机选一个元素
        swap(arr[i], arr[rand]);
    }
}
分析洗牌算法正确性的准则:产生的结果必须有 n! 种可能,否则就是错误的。
这个很好解释,因为一个长度为 n 的数组的全排列就有 n! 种,也就是说打乱结果总共有 n! 种。
洗牌算法 每次生成随机的左边界是i,每次都进1

i=0:1次选择 有 n次选择(0到n-1)
i=1:2次选择 有n-1次选择(1到n-1)
i=2:3次选择 有n-2次选择(2到n-1)……    显然是:   n!

所以下面这种做法是错的, 因为左边界一直没变 
跟i交换的索引一直是n种选择 所以有n*n*n…n (n的n次方)种

错误的索引生成方法:int rand = randInt(0, n); // 每次都从区间 [0, n)中随机选取元素进行交换

二、蒙特卡罗方法验证正确性

洗牌算法,正确性衡量标准是:
对于每种可能的结果出现的概率必须相等,也就是说要足够随机。
如果不用数学严格证明概率相等,可以用蒙特卡罗方法近似地估计出概率是否相等,结果是否足够随机。

我们可以对同一个数组进行一百万次洗牌,统计各种结果出现的次数,把频率作为概率,可以很容易看出洗牌算法是否正确。整体思想很简单,不过实现起来也有些技巧的:

每次进行洗牌算法后,就把得到的打乱结果对应的频数加一,重复进行 100 万次,如果每种结果出现的总次数差不多,那就说明每种结果出现的概率应该是相等的。作为一种验证方法,我们不需要 n 太大,一般用长度为 5 或 6 的 arr 试下就差不多了: 这个思路的伪代码:

void shuffle(int[] arr);

// 蒙特卡罗
int N = 1000000;
HashMap count; // 作为直方图
for (i = 0; i < N; i++) {
    int[] arr = {1,2,3};
    shuffle(arr);
    // 此时 arr 已被打乱
    count[arr] += 1}
for (int feq : count.values()) 
    print(feq / N + " "); // 频率

------------------------------------------ AC代码 --------------------------------------

    int[] copyArray;// 用来交换的拷贝数组
    int[] originalArray;// 原始数组
R	andom random = new Random();

    public Solution(int[] nums) {
        copyArray = new int[nums.length];
        originalArray = new int[nums.length];
        for (int i = 0; i < nums.length; ++i) {
            copyArray[i] = originalArray[i] = nums[i];
        }
    }
    
    public int[] reset() {
        return originalArray;
    }
    
    public int[] shuffle() {
        for (int i = 0; i < copyArray.length; ++i) {
            int randIndex = getRandIndex(i, copyArray.length);
            // 交换两个位置数字
            int tmp = copyArray[i];
            copyArray[i] = copyArray[randIndex];
            copyArray[randIndex] = tmp;
        }
        return copyArray;
	}

    int getRandIndex(int min, int max) {// 左闭右开
        // nextInt(n) 返回值num  0<=num<n 注意右边开区间
        return random.nextInt(max - min) + min;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值