题目
526. 优美的排列
难度:中等
假设有从 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。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/beautiful-arrangement
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
方法零:流氓方法
因为题目说明了N不会超过15,那就用例测试所有的数得到答案,然后用列表或者一串if
语句就可以通过了。
方法一:回溯
思路
本来就隐约觉得会用到回溯算法,但还是想先用排列组合的方法试一下,但太太困难了,最后还是用了回溯算法。
先创建一个字典d
,可以理解成键是第几个位置,对应的值是该位置上能放哪些数。
接下来是回溯函数,设置两个参数,第一个是用于记录当前状态的列表,第二个是 现在要填的是哪一个位置pos
,返回当前状态下的优美排列数c
。c
初始设为0,i
遍历可以在pos
位置上的所有数,即d[pos]
,如果i
不在列表之内,就将它填到列表的对应位置上,并c += func(s, pos+1)
,递归到下一个pos
中,直到pos==n
时,c += 1
。
可能还是直接看代码清楚一些吧。
代码
1 | def countArrangement(n): |
官方的回溯算法和我写得几乎一样,只是他用的是集合。我一开始也是想用集合的,但不知道集合的disturb
方法,才改成了列表。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Solution:
def countArrangement(self, n: int) -> int:
match = defaultdict(list)
for i in range(1, n + 1):
for j in range(1, n + 1):
if i % j == 0 or j % i == 0:
match[i].append(j)
num = 0
vis = set()
def backtrack(index: int) -> None:
if index == n + 1:
nonlocal num
num += 1
return
for x in match[index]:
if x not in vis:
vis.add(x)
backtrack(index + 1)
vis.discard(x)
backtrack(1)
return num
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/beautiful-arrangement/solution/you-mei-de-pai-lie-by-leetcode-solution-vea2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
方法二:状态压缩+动态规划
思路
这个方法实在难理解,借助官方题解下的评论才勉强看懂。也不知道什么叫状态压缩。
首先,是动态规划的状态表示。这里用二进制数表示,从右往左数,第i
位上的数字是1,就表示i
这个数字被选取了。创建长度为1<<n
的数组f
,表示某一状态下的优美排列数。
以n=6
举例。f[000110]
的意思是(这里下标里的数都是二进制数):数字2和3能够组成的优美排列数。
所以,最后返回的结果就是f[1<<n -1]
,即f[111111]
。
然后是转移方程。f[111111] = f[011111] + f[101111] + f[110111] + f[111011] + f[111101] + f[111110]
,也就是说,要算取了6个数的优美排列,就算上所有任意去掉这已经取了的6个数中的一个的状态的优美排列数。当然,事实上并不是所有的数都要相加,因为还需要判断,去掉的那个数能否放在第6个位置上。比如f[101111]
这时数字5
还没有取,而5
并不能放在第6个位置上。
再举个例子。f[100110]
是要算数字2
、3
和6
这3个数的优美排列,那就看哪个数可以放在第3个位置上,这里6
和3
可以放在第3个位置上,而这时候已经取了的数字分别是2
、3
和2
、6
所以f[100110] = f[000110] + f[100010]
。
因此,设计双重循环,外层mask
循环遍历所有状态,内存i
循环做的事情是从0(最右边)开始遍历mask
的每一位,如果第i+1
位上是1
,并且这个1
对应的数i+1
能放在这个位置上(这句话的意思是:假装还没有取i+1
这个数,此时已经取了num - 1
个数,那 i+1
能否放在num
这个位置上,所以,num
的意思就是要求的mask
当中已经取了的数的数量,所以,num
可以通过统计mask
中有几个1
来获得),那么f[mask]
就可以加上这种状态下的优美排列数了。
还是结合代码进行理解吧。
代码
1 | def countArrangement(n): |