[LeetCode] Find the Derangement of An Array 找数组的错排

简介: In combinatorial mathematics, a derangement is a permutation of the elements of a set, such that no element appears in its original position.

In combinatorial mathematics, a derangement is a permutation of the elements of a set, such that no element appears in its original position.

There's originally an array consisting of n integers from 1 to n in ascending order, you need to find the number of derangement it can generate.

Also, since the answer may be very large, you should return the output mod 109 + 7.

Example 1:

Input: 3
Output: 2
Explanation: The original array is [1,2,3]. The two derangements are [2,3,1] and [3,1,2].

Note:
n is in the range of [1, 106].

这道题给了我们一个数组,让我们求其错排的个数,所谓错排就是1到n中的每个数字都不在其原有的位置上,全部打乱了,问能有多少种错排的方式。博主注意到了这道题又让对一个很大的数取余,而且每次那个很大的数都是109 + 7,为啥大家都偏爱这个数呢,有啥特别之处吗?根据博主之前的经验,这种结果很大很大的题十有八九都是用dp来做的,那么就建一个一维的dp数组吧,其中dp[i]表示1到i中的错位排列的个数。那么难点就是找递推公式啦,先从最简单的情况来看:

n = 1 时有 0 种错排

n = 2 时有 1 种错排 [2, 1]

n = 3 时有 2 种错排 [3, 1, 2], [2, 3, 1]

然后博主就在想知道了dp[2],能求出dp[3]吗,又在考虑是不是算加入数字3的情况的个数。结果左看右看发现没有啥特别的规律,又在想是不是有啥隐含的信息需要挖掘,还是没想出来。于是看了一眼标签,发现是Math,我的天,难道又是小学奥数的题?挣扎了半天最后还是放弃了,上网去搜大神们的解法。其实这道题是组合数学种的错排问题,是有专门的递归公式的。

我们来想n = 4时该怎么求,我们假设把4排在了第k位,这里我们就让k = 3吧,那么我们就把4放到了3的位置,变成了:

x x 4 x

我们看被4占了位置的3,应该放到哪里,这里分两种情况,如果3放到了4的位置,那么有:

x x 4 3

那么此时4和3的位置都确定了,实际上只用排1和2了,那么就相当于只排1和2,就是dp[2]的值,是已知的。那么再来看第二种情况,3不在4的位置,那么此时我们把4去掉的话,就又变成了:

x x x

这里3不能放在第3个x的位置,在去掉4之前,这里是移动4之前的4的位置,那么实际上这又变成了排1,2,3的情况了,就是dp[3]的值。

再回到最开始我们选k的时候,我们当时选了k = 3,其实k可以等于1,2,3,也就是有三种情况,所以dp[4] = 3 * (dp[3] + dp[2])。

那么递推公式也就出来了:

dp[i] = (i - 1) * (dp[i - 1] + dp[i - 2])

有了递推公式,代码就不难写了吧,参见代码如下:

解法一:

class Solution {
public:
    int findDerangement(int n) {
        if (n < 2) return 0;
        vector<long long> dp(n + 1, 0);
        dp[1] = 0; dp[2] = 1;
        for (int i = 3; i <= n; ++i) {
            dp[i] = (dp[i - 1] + dp[i - 2]) * (i - 1) % 1000000007;
        }
        return dp[n];
    }
};

下面这种解法精简了空间,因为当前值只跟前两个值有关系,所以没必要保留整个数组,只用两个变量来记录前两个值,并每次更新一下就好了,参见代码如下:

解法二:

class Solution {
public:
    int findDerangement(int n) {
        long long a = 0, b = 1, res = 1;
        for (int i = 3; i <= n; ++i) {
            res = (i - 1) * (a + b) % 1000000007;
            a = b;
            b = res;
        }
        return (n == 1) ? 0 : res;
    }
};

下面这种方法是对之前的递推公式进行了推导变形,使其只跟前一个数有关,具体的推导步骤是这样的:

我们假设 e[i] = dp[i] - i * dp[i - 1]

递推公式为:  dp[i] = (i - 1) * (dp[i - 1] + dp[i - 2])

将递推公式带入假设,得到:

e[i] = -dp[i - 1] + (n - 1) * dp[i - 2] = -e[i - 1]

从而得到 e[i] = (-1)^n

那么带回假设公式,可得: dp[i] = i * dp[i - 1] + (-1)^n

根据这个新的递推公式,可以写出代码如下:

解法三:

public:
    int findDerangement(int n) {
        long long res = 1;
        for (int i = 1; i <= n; ++i) {
            res = (i * res + (i % 2 == 0 ? 1 : -1)) % 1000000007; 
        }
        return res;
    }
};

参考资料:

https://discuss.leetcode.com/topic/94429/o-n-short-java-code

https://discuss.leetcode.com/topic/94767/java-dp-solution-with-explanation

https://discuss.leetcode.com/topic/94421/java-solution-with-explanation-by-using-staggered-formula

本文转自博客园Grandyang的博客,原文链接:[LeetCode] Find the Derangement of An Array 找数组的错排

,如需转载请自行联系原博主。

相关文章
|
5月前
|
测试技术 PHP 开发者
PHP 数组查找:为什么 `isset()` 比 `in_array()` 快得多?
PHP 数组查找:为什么 `isset()` 比 `in_array()` 快得多?
|
6月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
248 1
|
9月前
|
人工智能 Java
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
590 1
Java 中数组Array和列表List的转换
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
141 0
|
12月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
244 67
|
9月前
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
移动开发 运维 供应链
通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理;js数组元素检查的方法,some()的使用详解,array.some与array.every的区别(附实际应用代码)
array.some()可以用来权限检查、表单验证、库存管理、内容审查和数据处理等数据校验工作,核心在于利用其短路机制,速度更快,节约性能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
供应链 JavaScript 前端开发
通过array.every()实现数据验证、权限检查和一致性检查;js数组元素检查的方法,every()的使用详解,array.some与array.every的区别(附实际应用代码)
array.every()可以用来数据验证、权限检查、一致性检查等数据校验工作,核心在于利用其短路机制,速度更快,节约性能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
Web App开发 存储 前端开发
别再用双层遍历循环来做新旧数组对比,寻找新增元素了!使用array.includes和Set来提升代码可读性
这类问题的重点在于能不能突破基础思路,突破基础思路是从程序员入门变成中级甚至高级的第一步,如果所有需求都通过最基础的业务逻辑来做,是得不到成长的。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
用array.filter()来实现数据筛选、数据清洗和链式调用,相对于for循环更加清晰,语义化强,能显著提升代码的可读性和可维护性。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

热门文章

最新文章

  • 1
    Java 中数组Array和列表List的转换
    590
  • 2
    JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
    554
  • 3
    通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式、聚合统计、处理树结构数据和性能优化,reduce()的使用详解(附实际应用代码)
    1324
  • 4
    通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理;js数组元素检查的方法,some()的使用详解,array.some与array.every的区别(附实际应用代码)
    391
  • 5
    通过array.every()实现数据验证、权限检查和一致性检查;js数组元素检查的方法,every()的使用详解,array.some与array.every的区别(附实际应用代码)
    244
  • 6
    多维数组操作,不要再用遍历循环foreach了!来试试数组展平的小妙招!array.flat()用法与array.flatMap() 用法及二者差异详解
    157
  • 7
    别再用双层遍历循环来做新旧数组对比,寻找新增元素了!使用array.includes和Set来提升代码可读性
    181
  • 8
    Array.forEach实战详解:简化循环与增强代码可读性;Array.forEach怎么用;面对大量数据时怎么提高Array.forEach的性能
    126
  • 9
    深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
    444
  • 10
    JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
    824