假设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:
第 i 位的数字能被 i 整除
i 能被第 i 位上的数字整除
现在给定一个整数 N,请问可以构造多少个优美的排列?
示例1:
输入: 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。
思路:
方法一:优雅的暴力
暴力第i个位置上放哪一个数,遇到不合适的立马回溯回去。
class Solution {
private int ans;
public int countArrangement(int N) {
ans=0;
int[] nums=new int[N];
for(int i=1;i<=N;i++)
nums[i-1]=i;
permute(nums,0);
return ans;
}
private void permute(int[] nums,int id) {
if(id==nums.length)
ans++;
for(int i=id;i<nums.length;i++) {
swap(nums,i,id);
if(nums[id]%(id+1)==0 || (id+1)%nums[id]==0)
permute(nums,id+1);
swap(nums,i,id);
}
}
private void swap(int[] nums,int x,int y) {
int tmp=nums[x];
nums[x]=nums[y];
nums[y]=tmp;
}
}
方法二:回溯
这个方法背后的想法很简单。我们常数从 1 到 N 创建所有的排列。我们可以将一个数字放在一个特定位置并检查这个数字在这个位置的可除性。但我们还需要记录之前已经使用过哪些数字以免重复使用同一个数字。如果当前数字不能满足可除性要求,当前位置为这个数的所有排列我们就都不需要考虑了,这个剪枝可以让我们的搜索空间大大减少。我们通过将每个数字在每个位置进行检查来实现这一过程。
我们使用一个使用一个大小为 NN 的 “已使用数组” ,这里 visited[i]visited[i] 表示目前为止第 ii 个数字是否已经使用过,True 表示已经使用过, False 表示还没有使用过。
我们使用函数 calculate,它将从 1 到 N 所有还没有被使用过的数字放到当前位置 pospos,并检查是否满足可除性。如果 ii 放到当前位置 pospos 是满足要求的,我们就把 ii 放在当前位置 pospos 并继续考虑下一个位置 pos + 1pos+1,否则我们需要换一个数字放在当前位置。
class Solution {
private int ans;
public int countArrangement(int N) {
ans=0;
boolean[] visited=new boolean[N+1];
calculate(N,1,visited);
return ans;
}
private void calculate(int N,int pos,boolean[] visited) {
if(pos>N) ans++;
for(int i=1;i<=N;i++) {
if(!visited[i] && (pos%i==0 || i%pos==0)) {
visited[i]=true;
calculate(N,pos+1,visited);
visited[i]=false;
}
}
}
}