2741. 特别的排列

题目

给你一个下标从 0 开始的整数数组 nums,它包含 n 个互不相同的正整数。如果 nums 的一个排列满足以下条件,我们称它是一个特别的排列:

对于 0 <= i < n - 1 的下标 i,要么 nums[i] % nums[i+1] == 0,要么 nums[i+1] % nums[i] == 0

请你返回特别排列的总数目,由于答案可能很大,请将它对 10^9 + 7 取余后返回。

示例 1:

输入:nums = [2,3,6]
输出:2
解释:[3,6,2] 和 [2,6,3] 是 nums 两个特别的排列。

示例 2:

输入:nums = [1,4,3]
输出:2
解释:[3,1,4] 和 [4,1,3] 是 nums 两个特别的排列。

提示:

  • 2 <= nums.length <= 14
  • 1 <= nums[i] <= 10^9

代码

完整代码

#include <stdbool.h>
#include <stdio.h>

// 深度优先搜索(DFS)函数
void dfs(int* nums, int numsSize, int* cnt, bool* isUsed, int* arr, int index)
{
    if(index == numsSize) // 找到一种排列
    {
        (*cnt)++;
        return;
    }

    for (int i = 0; i < numsSize; i++)
    {
        if(isUsed[i])
        {
            continue;
        }
        if(arr[index - 1] % nums[i] == 0 || nums[i] % arr[index - 1] == 0)
        {
            arr[index] = nums[i];
            isUsed[i] = true;
            dfs(nums, numsSize, cnt, isUsed, arr, index+1);
            isUsed[i] = false;
        }
    }
    return;
}

// 主函数
int specialPerm(int* nums, int numsSize){
    if(numsSize == 2)
    {
        if(nums[0] % nums[1] == 0 || nums[1] % nums[0] == 0)
        {
            return 2;
        }
        return 0;
    }
    bool *isUsed = (bool*)calloc(numsSize, sizeof(bool));
    int *arr = (int*)calloc(numsSize, sizeof(int));
    int cnt = 0;
    for (int i = 0; i < numsSize; i++)
    {
        arr[0] = nums[i]; // 每个数字做起始依一次
        isUsed[i] = true;
        dfs(nums, numsSize, &cnt, isUsed, arr, 1);
        isUsed[i] = false;
    }
    free(isUsed);
    free(arr);
    return cnt % (1000000000+7);
}

思路分析

这套代码用了深度优先搜索(DFS)的方法。

整体思路是通过枚举所有可能的排列,检查每一个排列是否满足特别排列的条件。

拆解分析

  1. 深度优先搜索(DFS)函数
void dfs(int* nums, int numsSize, int* cnt, bool* isUsed, int* arr, int index)
{
    if(index == numsSize) // 找到一种排列
    {
        (*cnt)++;
        return;
    }

    for (int i = 0; i < numsSize; i++)
    {
        if(isUsed[i])
        {
            continue;
        }
        if(arr[index - 1] % nums[i] == 0 || nums[i] % arr[index - 1] == 0)
        {
            arr[index] = nums[i];
            isUsed[i] = true;
            dfs(nums, numsSize, cnt, isUsed, arr, index+1);
            isUsed[i] = false;
        }
    }
    return;
}

DFS 函数用于生成所有可能的排列,并检查每一个排列是否满足特别排列的条件。index 表示当前排列的下标,cnt 用于记录满足条件的排列数量,isUsed 数组用于标记某个元素是否已被使用,arr 用于存储当前的排列。

  1. 主函数
int specialPerm(int* nums, int numsSize){
    if(numsSize == 2)
    {
        if(nums[0] % nums[1] == 0 || nums[1] % nums[0] == 0)
        {
            return 2;
        }
        return 0;
    }
    bool *isUsed = (bool*)calloc(numsSize, sizeof(bool));
    int *arr = (int*)calloc(numsSize, sizeof(int));
    int cnt = 0;
    for (int i = 0; i < numsSize; i++)
    {
        arr[0] = nums[i]; // 每个数字做起始依一次
        isUsed[i] = true;
        dfs(nums, numsSize, &cnt, isUsed, arr, 1);
        isUsed[i] = false;
    }
    free(isUsed);
    free(arr);
    return cnt % (1000000000+7);
}

主函数首先处理特殊情况,当数组大小为 2 时,直接判断两元素是否互为倍数。然后使用 DFS 方法枚举所有排列,统计满足条件的排列数目,并返回对 10^9 + 7 取余后的结果。

复杂度分析

  • 时间复杂度:由于使用 DFS 方法生成所有排列,对于 n 个元素,排列的总数为 n!。在每一步中检查两个元素的倍数关系,因此时间复杂度为 O(n! * n)
  • 空间复杂度:使用了额外的数组 isUsedarr,每个数组的空间复杂度为 O(n)

一题多解

动态规划(DP)解法

完整代码

#include <stdio.h>
#include <stdbool.h>

// 动态规划解法
int specialPermDP(int* nums, int numsSize) {
    int MOD = 1000000007;
    int dp[1 << numsSize][numsSize];
    memset(dp, 0, sizeof(dp));

    for (int i = 0; i < numsSize; i++) {
        dp[1 << i][i] = 1;
    }

    for (int mask = 1; mask < (1 << numsSize); mask++) {
        for (int i = 0; i < numsSize; i++) {
            if ((mask & (1 << i)) == 0) continue;
            for (int j = 0; j < numsSize; j++) {
                if (mask & (1 << j)) continue;
                if (nums[i] % nums[j] == 0 || nums[j] % nums[i] == 0) {
                    dp[mask | (1 << j)][j] = (dp[mask | (1 << j)][j] + dp[mask][i]) % MOD;
                }
            }
        }
    }

    int result = 0;
    for (int i = 0; i < numsSize; i++) {
        result = (result + dp[(1 << numsSize) - 1][i]) % MOD;
    }
    return result;
}

思路分析

这个解法使用了动态规划(DP)的方法。通过掩码 mask 来表示当前排列的状态,dp[mask][i] 表示当前排列状态为 mask,最后一个元素是 nums[i] 的排列数。

拆解分析

1. 初始化状态

在动态规划解法中,我们首先初始化每个元素作为起始元素的状态:

for (int i = 0; i < numsSize; i++) {
    dp[1 << i][i] = 1;
}

这段代码的意思是,对于每个元素 nums[i],我们将其作为排列的起始元素,这样的排列数初始为 1。这里 1 << i 表示一个掩码,其中只有第 i 位是 1,表示元素 nums[i] 已经被使用。

2. 状态转移

动态规划的核心部分是状态转移。我们使用一个掩码 mask 来表示当前排列的状态,其中掩码的第 i 位为 1 表示元素 nums[i] 已经被使用。dp[mask][i] 表示当前排列状态为 mask,且最后一个元素是 nums[i] 的排列数。

我们遍历所有可能的状态和元素,通过以下代码更新状态转移:

for (int mask = 1; mask < (1 << numsSize); mask++) { // 遍历所有可能的状态
    for (int i = 0; i < numsSize; i++) { // 遍历所有元素作为当前排列的最后一个元素
        if ((mask & (1 << i)) == 0) continue; // 如果当前元素没有被使用,跳过
        for (int j = 0; j < numsSize; j++) { // 尝试将每个元素加入到当前排列的末尾
            if (mask & (1 << j)) continue; // 如果元素已经被使用,跳过
            if (nums[i] % nums[j] == 0 || nums[j] % nums[i] == 0) { // 检查是否满足条件
                dp[mask | (1 << j)][j] = (dp[mask | (1 << j)][j] + dp[mask][i]) % MOD;
            }
        }
    }
}
  • for (int mask = 1; mask < (1 << numsSize); mask++):遍历所有可能的状态,mask 的范围是 12^numsSize - 1。每个状态用一个二进制数表示,例如 mask = 5 对应的二进制是 0101,表示第 0 和第 2 个元素已被使用。
  • for (int i = 0; i < numsSize; i++):遍历所有元素作为当前排列的最后一个元素。
  • if ((mask & (1 << i)) == 0) continue:如果当前元素没有被使用,跳过。
  • for (int j = 0; j < numsSize; j++):尝试将每个元素加入到当前排列的末尾。
  • if (mask & (1 << j)) continue:如果元素已经被使用,跳过。
  • if (nums[i] % nums[j] == 0 || nums[j] % nums[i] == 0):检查 nums[i]nums[j] 是否满足特别排列的条件。
  • dp[mask | (1 << j)][j] = (dp[mask | (1 << j)][j] + dp[mask][i]) % MOD:如果满足条件,则更新状态 dp[mask | (1 << j)][j],表示将 nums[j] 加入到当前排列末尾后的新状态。
3. 计算结果

最终结果为所有状态为 (1 << numsSize) - 1 且最后一个元素为 i 的排列数之和:

int result = 0;
for (int i = 0; i < numsSize; i++) {
    result = (result + dp[(1 << numsSize) - 1][i]) % MOD;
}
  • for (int i = 0; i < numsSize; i++):遍历所有元素作为最后一个元素的排列。
  • result = (result + dp[(1 << numsSize) - 1][i]) % MOD:将所有状态为 (1 << numsSize) - 1(即所有元素都被使用)且最后一个元素为 i 的排列数累加到结果中,并对 10^9 + 7 取余。

通过上述过程,我们可以计算出所有满足条件的特别排列的总数目。

复杂度分析

  • 时间复杂度:动态规划状态数为 2^n ,每个状态的转移时间为 O(n^2),因此总时间复杂度为 O(n^2 * 2^n)
  • 空间复杂度:使用了 dp 数组,空间复杂度为 O(n * 2^n)

结果

DFS 和 DP 是两种常见的解决排列组合问题的方法。DFS 通过递归遍历所有可能的排列,DP 通过状态转移记录每个状态的排列数。两种方法在不同的场景下有不同的优势和劣势。

DFS:

O(n!*n)还是不彳亍
在这里插入图片描述

DP:

彳亍
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值