Suppose you have n
integers labeled 1
through n
. A permutation of those n
integers perm
(1-indexed) is considered a beautiful arrangement if for every i
(1 <= i <= n
), either of the following is true:
perm[i]
is divisible by i
.i
is divisible by perm[i]
.Given an integer n
, return the number of the beautiful arrangements that you can construct.
Example 1:
Input: n = 2 Output: 2 Explanation: The first beautiful arrangement is [1,2]: - perm[1] = 1 is divisible by i = 1 - perm[2] = 2 is divisible by i = 2 The second beautiful arrangement is [2,1]: - perm[1] = 2 is divisible by i = 1 - i = 2 is divisible by perm[2] = 1
Example 2:
Input: n = 1 Output: 1
Constraints:
1 <= n <= 15
This problem asks us to find the number of beautiful arrangements of integers from 1 to n. A beautiful arrangement is a permutation where for each index i, either the element at that index is divisible by i, or i is divisible by the element at that index.
The most efficient way to solve this problem is using backtracking (or Depth-First Search - DFS). We can also pre-compute possible matches to speed up the process slightly.
Approach:
Pre-compute Matches: For each number i
from 1 to n
, we create a list of numbers j
(from 1 to n
) such that i
is divisible by j
or j
is divisible by i
. This optimization reduces redundant computations during the backtracking process.
Backtracking (DFS): We use a recursive function dfs(index)
where index
represents the current position in the permutation we are constructing.
index
is n + 1
, it means we have built a complete permutation. We increment the count of beautiful arrangements (ans
).j
in the list of matches for index
, if j
has not been used (checked via vis
array), we mark j
as used, recursively call dfs(index + 1)
, and then unmark j
to explore other possibilities.Return Count: Finally, we return the total count of beautiful arrangements (ans
).
Time Complexity Analysis:
n
and a branching factor of n
in some nodes (although this is unlikely in practice). Therefore, the time complexity is approximately O(n!). However, the actual runtime is significantly less due to the pruning done by the vis
array, which eliminates many branches early on.Space Complexity Analysis:
vis
of size n+1
to track used numbers, requiring O(n) space.match
array (or hash map/dictionary) also requires O(n^2) space in the worst case, though in practice the space used will be much less.n
, requiring O(n) space in the worst case.Therefore, the overall space complexity is dominated by the pre-computation, which is O(n^2).
Code (Python):
from collections import defaultdict
class Solution:
def countArrangement(self, n: int) -> int:
ans = 0
vis = [False] * (n + 1)
match = defaultdict(list)
for i in range(1, n + 1):
for j in range(1, n + 1):
if j % i == 0 or i % j == 0:
match[i].append(j)
def dfs(i):
nonlocal ans, n
if i == n + 1:
ans += 1
return
for j in match[i]:
if not vis[j]:
vis[j] = True
dfs(i + 1)
vis[j] = False
dfs(1)
return ans
The code in other languages (Java, C++, Go, TypeScript, Rust) follows a similar structure and logic, only differing in syntax and data structures. They all share the same time and space complexity characteristics.