leetcode
leetcode 551 ~ 600
寻找数组的错位排列

寻找数组的错位排列

难度:

标签:

题目描述

代码结果

运行时间: 150 ms, 内存: 15.9 MB


/*
 * 寻找数组的错位排列
 * 题目描述:给定一个数组,你需要找到这个数组的错位排列。
 * 错位排列指的是数组中元素的位置和它的初始位置不相同。
 * 思路:
 * 1. 使用Java Stream生成所有的排列。
 * 2. 使用过滤器检查每个排列是否是错位排列。
 */
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
public class DerangementStream {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        List<int[]> result = findDerangements(nums);
        result.forEach(arr -> System.out.println(Arrays.toString(arr)));
    }
 
    public static List<int[]> findDerangements(int[] nums) {
        List<int[]> permutations = permute(nums);
        return permutations.stream()
                .filter(DerangementStream::isDerangement)
                .collect(Collectors.toList());
    }
 
    private static List<int[]> permute(int[] nums) {
        if (nums.length == 0) {
            return Collections.singletonList(new int[0]);
        }
        return IntStream.range(0, nums.length).boxed()
                .flatMap(i -> {
                    int[] subArray = IntStream.concat(IntStream.of(nums).limit(i), IntStream.of(nums).skip(i + 1)).toArray();
                    return permute(subArray).stream().map(tail -> {
                        int[] result = new int[nums.length];
                        result[0] = nums[i];
                        System.arraycopy(tail, 0, result, 1, tail.length);
                        return result;
                    });
                }).collect(Collectors.toList());
    }
 
    private static boolean isDerangement(int[] nums) {
        return IntStream.range(0, nums.length).allMatch(i -> nums[i] != i + 1);
    }
}

解释

方法:

这个题解使用动态规划的思想来解决错位排列问题。我们用 f[i] 表示长度为 i 的数组的错位排列数目,那么可以得到递推式:f[i] = (i-1) * (f[i-1] + f[i-2])。这是因为我们可以考虑数字 i 放在哪里:如果把 i 放在某个位置,其余 i-1 个数有 f[i-1] 种错位排列方式;如果不把 i 放在任何位置,那么就相当于求 i-1 个数的错位排列数,即 f[i-2]。为了避免重复计算和节省空间,我们可以只用两个变量 f1 和 f2 来分别表示 f[i-1] 和 f[i-2],并在每次循环中更新它们的值。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在计算错位排列数时递推式为`f[i] = (i-1) * (f[i-1] + f[i-2])`?这里的`(i-1)`和`(f[i-1] + f[i-2])`分别代表什么意义?
这个递推式基于错位排列的定义和组合思考。错位排列意味着数组中没有一个元素出现在其原始位置上。考虑将元素`i`放入数组的错位排列中:1) 如果我们把元素`i`置于除了最后一个位置外的任何一个位置(共有`i-1`个选择),那么剩下的`i-1`个元素中的每一个也不能出现在它的原始位置上,即`f[i-1]`;2) 我们也可以考虑将元素`i`放在最后一个位置,这时我们需要将前`i-1`个元素进行错位排列,即`f[i-2]`。因此,`f[i]`的值由这两部分组成,`(i-1)`代表元素`i`可以选择的位置数量(除去它自己的原始位置),`f[i-1] + f[i-2]`代表在这些位置选择后,其他元素的错位排列数量。
🦆
你是如何确定基础情况`f[0]`和`f[1]`的值分别为0和0,以及`f[2]`为1的?这些情况有何特殊之处?
基础情况是理解错位排列的起点。对于`f[0]`,考虑一个空数组,没有元素进行错位排列,因此结果为0。对于`f[1]`,只有一个元素的数组不能满足错位排列的条件,因为唯一的元素只能放在它的原始位置,故也是0。而对于`f[2]`,包含两个元素的数组,唯一的错位排列是将这两个元素互换位置,因此错位排列数为1。这些基础情况为递推式提供了初始值,使得我们可以计算更大的`n`值。
🦆
在动态规划的更新过程中,使用`(i - 1) * (f1 + f2) % MOD`来避免整数溢出,但为什么选择这个特定的模`10**9 + 7`?
模`10**9 + 7`的选择是基于几个方面的考虑。首先,这是一个大质数,可以有效避免在计算过程中的冲突和整数溢出问题。其次,由于这个数接近于`10^9`,它在计算机中可以高效地处理,同时保持良好的性能特性。最后,`10**9 + 7`是在编程竞赛和算法实现中常用的模数,因此在算法设计中使用该模数有助于保持一致性和标准化。

相关问题