Leetcode-526 优美的排列
第二天再写博客是真的有用,今天写昨天做的题,第一眼竟然是我做过吗?真的离谱,第二天做还是有助于回忆的。
题目
假设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:
第 i 位的数字能被 i 整除
i 能被第 i 位上的数字整除
现在给定一个整数 N,请问可以构造多少个优美的排列?
输入: 2
输出: 2
解释:
第 1 个优美的排列是 [1, 2]:
第 1 个位置(i=1)上的数字是1,1能被 i(i=1)整除
第 2 个位置(i=2)上的数字是2,2能被 i(i=2)整除
第 2 个优美的排列是 [2, 1]:
第 1 个位置(i=1)上的数字是2,2能被 i(i=1)整除
第 2 个位置(i=2)上的数字是1,i(i=2)能被 1 整除
说明:
- N 是一个正整数,并且不会超过15。
解答
方法一 DFS
这里我看到题目第一眼想到的就是回溯,当然我更喜欢叫搜索。这一题其实与全排列差不多,只是对每个位置多加了一个限制条件,也就是第 i 位的数字 j,需要 i 能整除 j 或者 j 能整除 i。这样只需要在排列的时候多加一个判断条件即可,如果不满足就返回。
具体思路是先用一个 set 集合存储所有已经使用过的数字。从第一个位置开始判断,第一个位置有可能是从 1 ~ n 的 n 种可能,那么第 2 个位置上的数字有 n -1 种可能。用一个 for 循环检查每一位的所有可能情况,如果没被使用过且可以被整除,就搜索下去,当所有数字用完,说明找到了一个组合,排列数加一。
class Solution {
public:
int res = 0;
set<int> se;
int countArrangement(int n) {
dfs(0, 1, n);
return res;
}
void dfs(int cnt, int num, int maxn) {
if (cnt == maxn) {
res++;
return ;
}
for (int i = 1; i <= maxn; i++) {
if (se.count(i))
continue;
if (i % (cnt + 1) != 0 && (cnt + 1) % i != 0)
continue;
se.insert(i);
dfs(cnt + 1, i, maxn);
se.erase(i);
}
}
};
方法二 状态压缩
因为 n 小于 15,可以使用一个二进制来表示当前哪些位可选,哪些位不可选。假设变量 state 存储了当前的数字使用情况,也就是 state 的二进制对应为 1 的位已经被使用了,并且 state 为 1 的位共有 k 个。那么 state 对应的下一个选择的情况为遍历的数字多一,也就是 state 中的一个为 0 的位会变为 1,同时变化的位与 k + 1 可以整除。
那么定义二维数组 vec[i][state],i 表示正在给位置 i 选择数据,并且前 i -1 个数据选择完毕,state 表示给位置 i 选择完毕后二进制数的状态。考虑状态转移,很明显,f[i] 需要由 f[i - 1] 转化而成。对于状态 state,它的前一个状态应该是 state 中的某一位为 0 时对应的数据,用公式表示就是 state & (!(1 << k))(0 <= k < n,且 state 的第 i 位为 1)。而 state 为 1 的位很可能不止一个,因此需要进行累加。具体代码表示如下:
class Solution {
public:
int countArrangement(int n) {
int mask = 1 << n;
vector<vector<int>> vec(n + 1, vector<int>(mask, 0));
vec[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int state = 0; state < mask; state++) {
for (int k = 1; k <= n; k++) {
if (((state >> (k - 1)) & 1) == 0)
continue;
if (k % i && i % k)
continue;
vec[i][state] += vec[i - 1][state & (~(1 << (k - 1)))];
}
}
}
return vec[n][mask - 1];
}
};