526. 优美的排列
思路:
动态规划 + 状态压缩
状态压缩:
最多的数字个数是15,所以可以使用一个int型变量,每一位代表是否选择当前数字。mask代表选取的位置,由于是由0位开始的,所以第i位实际上是第i+1位数字。
动态规划:
每一次选取后,下一次选取在该次选取的基础上再次选取。
状态转移方程
当我们想要计算当前的f[mask]时,需要把前num(mask - 1) 放置好,,最后再将本次计算结果累加上去。我们需要依次枚举本次可以被放置的数,最后加起来。
最终的结果在f[2^n - 1] 这里呈现。
/**
* 526. 优美的排列
*/
public class Solution526 {
public int countArrangement(int n) {
// 结果集个数 最多 2^N - 个
// 用来存储中间结果,res[6] = res[000110] = 数字2、3在前两位时的完美排列数量
int[] res = new int[1 << n];
// 1 肯定能放在第一位,结果个数为1
res[0] = 1;
// mask 使用状态压缩,将数组压缩成一个数字
for (int mask = 1; mask < (1 << n); mask++) {
// num 二进制中数字1的个数,1代表当前数字可以存放,0代表当前数字不可以存放
int num = Integer.bitCount(mask);
// i + 1 代表当前的数字,i 代表 当前数字对应的在mask中间的位置
// 遍历 mask 的每一位,仍以 mask = 100110 为例,此 mask 代表 2 3 6三个数字在排列的前三位
// 求三个数字 2 3 6 的完美排列方式,则先确定2 3 6哪些数字能放到第三位,然后累加另外两个数字的完美排列数量来获得
for (int i = 0; i < n; i++) {
// mask & (1 << i) 检查当前位置是否被选取
if ((mask & (1 << i)) != 0) {
// ((num % (i + 1)) == 0 || (i + 1) % num == 0) 判断被选取的数字 i+1 能否放到位置 num 上,
// 即:先从被选取的数字中找到能放到最高位 num 的数字,然后将剩余 num-1 个数字的完美排列方式累加到res[mask]中
if (num % (i + 1) == 0 || (i + 1) % num == 0) {
// 00010101 ^
// 00000110 =
// 00010011
// mask ^ (1 << i) 将 mask 第 i 位设置为 0
// 2 3 6,第三位可以为 6,则 res[100110] += res[000110] (2、3在前两位时的完美排列数量)
// 2 3 6,第三位可以为 3,则 res[100110] += res[100010] (2、6在前两位时的完美排列数量)
res[mask] += res[mask ^ (1 << i)];
}
}
}
}
return res[1 << n - 1];
}
public static void main(String[] args) {
Solution526 solution526 = new Solution526();
System.out.println(solution526.countArrangement(6));
System.out.println(solution526.countArrangement(12));
}
}