秒懂错排----动态规划

秒懂错排----动态规划

什么是错排

错排就是一个数组要满足所有元素都不能在他原来的位置上,要求出这个数组所有的错排数目,如下图:
在这里插入图片描述在这里插入图片描述给出一个数字n,给出所有错排组合的数目,这种类似的题牛客上也有,就是信件邮箱和小朋友坐座位,都是求出所有的错排的组合,这道题有两种算法,一种的是用递归,一种的动态规划。

先说递归,还记得排列组合的算法是怎样写了的吗,忘了不用着急,下面就是排列组合的代码,而错排组合就是在排列组合的基础上加上一些限制条件就行了,请看下面代码:
这个是排列组合的代码

public int rangeNums(int n) {
        int[] array = new int[n];
        for(int i = 0; i < n; i++) {
            array[i] = i;
        }
        //得到该数组的所有排列组合
        return recurrence(array, 0);
}

public int recurrence(int[] nums, int index) {
        if(index == nums.length) {
            return 1;
        }
        int result = 0;
        for(int i = index; i < nums.length; i++) {
            swap(nums, i, index);
            //把每一个从index开始排列组合的情况都加起来
            result += recurrence(nums, index + 1);
            swap(nums, i, index);
        }
        return result;
}

    public void swap(int[] nums, int n, int m) {
        int temp = nums[m];
        nums[m] = nums[n];
        nums[n] = temp;
}

只要在这个排列组合的代码上加上错排的条件限制就可以了,代码如下,观察两个代码的异同:
错排的代码

public int rangeNums(int n) {
        int[] array = new int[n];
        for(int i = 0; i < n; i++) {
            array[i] = i;
        }
        //得到该数组的所有排列组合
        return recurrence(array, 0);
}

public int recurrence(int[] nums, int index) {
        if(index == nums.length) {
            return 1;
        }
        int result = 0;
        for(int i = index; i < nums.length; i++) {
            swap(nums, i, index);
            //把每一个从index开始排列组合的情况都加起来
           if(i != nums[i]) {
           //如果nums[i]原来不再这个座位上,就满足错排的定义
                result += recurrence(nums, index + 1);
            }
            swap(nums, i, index);
        }
        return result;
}

    public void swap(int[] nums, int n, int m) {
        int temp = nums[m];
        nums[m] = nums[n];
        nums[n] = temp;
}

上面就是递归的代码,缺点可见,效率太低了,如果n = 100,那上面的代码就炸了。好的,主角登场,动态规划。直接开门见山,通常动态规划都要有一个dp[]数组,而且每个dp[i]是有意义的,在这我们这样子定义。
***dp[i] 表示的是从1…i之间所有元素的错排数目
好的,我们以1, 2, 3, 4, 5这个例子来理解为什么这道题可以用动态规划,我们定义一个dp[]数组,假设我们已经找到dp[1]…dp[4]的值,如何来求dp[5]的值
dp[1] = 0
dp[2] = 1
dp[3] = 2
dp[4] = 9
到了第5个,因为5能放的位子只有1,2,3,4一共4个位子,我们假设把5放第1个位置,然后就分两种情况,把1放到第5个位置和把1放到2,3,4个位置。

第一种情况把1放到第5 个位置,剩下的位置和元素分别为2,3,4和2,3,4,这也是错排组合的问题等于dp[3]了。

第二种情况不把1放到第5个位置,那剩下的位置和元素分别为2,3,4,5和1,2,3,4其中:
1不能放在5位置
2不能放在2位置
3不能放在3位置
4不能放在4位置
可以看出来是个元素数目为4的错排组合数目等于dp[4]
所以dp[5] = (5 - 1所以可以得出结论dp[n] = (n -1)(dp[n - 2] + dp[n - 1])
代码如下

public int erroNum(int n){
    if(n=0)
        return 0;
    if(n=1)
        return 0;
    int []dp=new int [n+1];
    dp[0]=0;
    dp[1]=0;
    dp[2]=1;
    for(int i=3;i<=n;i++){
        dp[i]=(i-1)*(dp[i-1]+dp[i-2]);
    }
    return dp[n];
}

细心的读者发现了,其实我们只用到了数组的前两个数据,再前面的就浪费了,没错再这里我们可以用变量代替数组就可以了,代码如下:

public int erroNum(int n){
    if(n=0)
        return 0;
    if(n=1)
        return 0;
   int pre1 = 0; //用来代替dp[n - 2]
   int pre2 = 1;//用来代替dp[n - 1]
   int now = 0;
    for(int i=3;i<=n;i++){
        now = (i -1)(pre1 + pre2);
        pre1 = pre2;
        pre2 = now;
    }
    return now;
}

和递归相比,只用了O(n)的复杂度,而递归的复杂度是O(n!),其实在很多算法题中,基本都是动态规划的效率往往比递归的算法高,这也是因为动态规划是牺牲了空间换取时间的一个原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值