2741. 特别的排列
难度:中等
时间:2023/06/30
给你一个下标从 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 <= 141 <= nums[i] <= 10^9
class Solution {
public:
int specialPerm(vector<int>& nums) {
int n = nums.size();
int dp[1 << n][n];
int MOD = 1e9 + 7;
memset(dp, 0, sizeof dp);
for (int i = 0; i < (1 << n); ++i) {
for (int j = 0; j < n; ++j) {
if ((i >> j) & 1) {
int ni = i ^ (1 << j);
if (ni == 0) {
dp[i][j] = 1;
continue;
}
for (int k = 0; k < n; ++k) {
if (nums[k] % nums[j] == 0 || nums[j] % nums[k] == 0)
dp[i][j] = (dp[i][j] + dp[ni][k]) % MOD;
}
}
}
}
int ans = 0;
for (int i = 0; i < n; ++i)
ans = (ans + dp[(1 << n) - 1][i]) % MOD;
return ans;
}
};
总结: 对于 nums 数组中任意一个元素都有选与不选两种状态,因此考虑使用状态压缩dp进行求解。
设置二维数组 dp[1 << n][n] ,第一维表示 nums 数组中所有元素是否被选择的状态,对应的位数上为 1 则表示被选;第二维 j 表示所有被选的元素组成的序列中,以 nums[j] 为最后一个的序列。
则状态转移方程为: dp[i][j] = sum(dp[i ^ (1 << j)][k],其中 i ^ (1 << j) 表示不选取第 j 位元素的状态,将所有该状态下以任意元素 nums[k] 结尾的序列个数累加起来。
class Solution:
def specialPerm(self, nums: List[int]) -> int:
n = len(nums)
mod = 10 ** 9 + 7
@lru_cache(None)
def dfs(state, x):
if not state:
return 1
ret = 0
for j in range(n):
if (1 << j) & state and (x % nums[j] == 0 or nums[j] % x == 0):
ret = (ret + dfs(state ^ (1 << j), nums[j])) % mod
return ret % mod
return dfs((1 << n) - 1, 0)
1.不用考虑组合顺序,只用在乎最后一个元素就可以,只用判断当前要添加的数是否可可以接入之前的状态(题目要求的规则) 2 由于数据范围很小 可以使用状态压缩就可以 2.1 每个数据都给一个问题的二进制编码 例如 011101 表示状态 从右向左开始遍历 最右侧是 0位 以此类推 dp[011101][0] 可以从 dp[011100][x]推断来下 x表示 011100对应位的编码 且满足题目要求 3 最后累加 dp[1<nums.length-1][0~nums.length-1]