题目描述:
假设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:
第 i 位的数字能被 i 整除
i 能被第 i 位上的数字整除
现在给定一个整数 N,请问可以构造多少个优美的排列?
示例1:
说明:
题目分析:
- 条件一:第 i 位的数字能被 i 整除,条件二:i 能被第 i 位上的数字整除
- 假设数组arr索引i,则 arr[i] % i == 0 || i % arr[i] == 0,需要满足其中一个。
- N 是一个正整数,并且不会超过15。
- n的最大值15,小于int的32位,这代表可以使用状态压缩的方式来记录当前索引位置是否已被使用。
思路一:
代码实现:
class Solution {
public int result;
public boolean[] flag;
public int countArrangement(int n) {
result = 0;
flag = new boolean[n + 1];
dfs(1, n);
return result;
}
public void dfs(int idx, int n) {
if (idx > n) { // 超过最大数字n,返回。
result++; //
return;
}
for (int i = 1; i <= n; i++) {
if (!flag[i] && (idx % i == 0 || i % idx == 0)) {
flag[i] = true; // 数组该索引位置已经存放了数字
dfs(idx + 1, n); // 下一个数字
flag[i] = false; // 删除该索引位置存放的数字
}
}
}
}
思路二:
因为n的最大值只有15,因此可以使用状态压缩的方式来记录已经被使用的数组索引。
- 索引i位置是否已被使用:
- (1 << i) & state == 0 未被使用
- (1 << i) & state == 1 已被使用
- 标记索引i位置为使用状态:
- (1 << i) | state
- 所有位置是否都被使用
- state ==(1 << n) - 1
代码实现:
class Solution {
public int result;
public int countArrangement(int n) {
result = 0;
dfs(1, n, 0);
return result;
}
public void dfs(int idx, int n, int state) {
if (state == ((1 << n) - 1)) { // 数组所有位置都被使用
result++;
return;
}
for (int i = 1; i <= n; i++) {
if (((1 << (i - 1)) & state) == 0 && (idx % i == 0 || i % idx == 0)) {
dfs(idx + 1, n, ((1 << (i - 1)) | state)); // 下一个数字
}
}
}
}
思路三:
通过观察思路二,可以发现有很多相同场景,反复进行了遍历,可以记录下来。避免重复递归。
代码实现:
class Solution {
public int result;
public int[][] arr;
public int countArrangement(int n) {
result = 0;
arr = new int[n + 1][1 << n];
result = dfs(1, n, 0);
return result;
}
public int dfs(int idx, int n, int state) {
if (state == (1 << n) - 1) return 1; // 所有数字都已经被使用
if (arr[idx][state] != 0) return arr[idx][state]; // 数组索引idx,索引使用位置状态state的情况已经遍历过,直接返回
int temp = 0;
for (int i = 1; i <= n; i++) {
if (((1 << (i - 1)) & state) == 0 && (idx % i == 0 || i % idx == 0)) {
temp += dfs(idx + 1, n, ((1 << (i - 1)) | state)); // 下一个数字
}
}
arr[idx][state] = temp; // 记录索引idx,数组索引使用状态state时的数量。
return temp;
}