Beautiful Arrangement
Imagine you are arranging numbered tiles in a row, but only certain positions are allowed for each tile based on divisibility rules. How many beautiful arrangements can you create?
Given an integer n, count the number of the beautiful arrangements that you can construct. A beautiful arrangement is defined as an array of the numbers from 1 to n such that for every position i (1-indexed), either the number at position i is divisible by i or i is divisible by the number.
1 ≤ n ≤ 15"n = 2"2The two beautiful arrangements are [1,2] and [2,1]. For position 1, 1 divides 1 and 2 divides 1 (false), but 1 divides 2 (true). For position 2, 2 divides 2 and 1 divides 2.
- n = 1 → output: 1 (only one number, trivially beautiful)
- n = 0 → output: 0 (no numbers, no arrangements)
- n = 15 → output: large number, tests efficiency
- n = 3 → output: 3 (small input with multiple valid permutations)
Code runs too slowly and times out on larger inputs
✅ Add divisibility checks before recursing deeper to prune invalid branches
Incorrect count or wrong results
✅ Remember positions are 1-indexed; adjust indices accordingly
Misses valid permutations or counts duplicates
✅ Always unmark used numbers after recursive calls
Numbers appear used or unused incorrectly, leading to wrong counts
✅ Use 1 << (num - 1) with num starting from 1, or adjust indexing consistently
Intuition
Generate every permutation of numbers 1 to n, then check if each permutation satisfies the divisibility condition at every position.
Algorithm
- Generate all permutations of numbers from 1 to n.
- For each permutation, check if for every position i, the number at position i divides i or i divides the number.
- Count the permutations that satisfy the condition.
- Return the count.
from itertools import permutations
def countArrangement(n: int) -> int:
count = 0
for perm in permutations(range(1, n+1)):
if all((perm[i] % (i+1) == 0) or ((i+1) % perm[i] == 0) for i in range(n)):
count += 1
return count
# Driver code
if __name__ == '__main__':
print(countArrangement(2)) # Output: 2from itertools import permutationsImport permutations to generate all possible orderings of numbersfor perm in permutations(range(1, n+1))Iterate over every possible permutation of numbers 1 to nall((perm[i] % (i+1) == 0) or ((i+1) % perm[i] == 0) for i in range(n))Check the divisibility condition for every position in the permutationcount += 1Increment count if the current permutation is a beautiful arrangementimport java.util.*;
public class BeautifulArrangement {
public static int countArrangement(int n) {
List<Integer> nums = new ArrayList<>();
for (int i = 1; i <= n; i++) nums.add(i);
return permute(nums, 0);
}
private static int permute(List<Integer> nums, int start) {
if (start == nums.size()) {
for (int i = 0; i < nums.size(); i++) {
int pos = i + 1;
int val = nums.get(i);
if (val % pos != 0 && pos % val != 0) return 0;
}
return 1;
}
int count = 0;
for (int i = start; i < nums.size(); i++) {
Collections.swap(nums, i, start);
count += permute(nums, start + 1);
Collections.swap(nums, i, start);
}
return count;
}
public static void main(String[] args) {
System.out.println(countArrangement(2)); // Output: 2
}
}for (int i = 1; i <= n; i++) nums.add(i);Initialize list with numbers 1 to nif (start == nums.size())Base case: all positions fixed, check validityif (val % pos != 0 && pos % val != 0) return 0;Check divisibility condition for each positionCollections.swap(nums, i, start);Swap elements to generate permutations#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int count = 0;
bool isBeautiful(const vector<int>& arr) {
for (int i = 0; i < arr.size(); i++) {
int pos = i + 1;
int val = arr[i];
if (val % pos != 0 && pos % val != 0) return false;
}
return true;
}
void permute(vector<int>& arr, int start) {
if (start == arr.size()) {
if (isBeautiful(arr)) count++;
return;
}
for (int i = start; i < arr.size(); i++) {
swap(arr[i], arr[start]);
permute(arr, start + 1);
swap(arr[i], arr[start]);
}
}
int main() {
int n = 2;
vector<int> arr;
for (int i = 1; i <= n; i++) arr.push_back(i);
permute(arr, 0);
cout << count << endl; // Output: 2
return 0;
}for (int i = 1; i <= n; i++) arr.push_back(i);Initialize vector with numbers 1 to nif (start == arr.size())Base case: all positions fixed, check if arrangement is beautifulif (val % pos != 0 && pos % val != 0) return false;Check divisibility condition for each positionswap(arr[i], arr[start]);Swap elements to generate permutationsfunction countArrangement(n) {
let count = 0;
const nums = [];
for (let i = 1; i <= n; i++) nums.push(i);
function permute(arr, start) {
if (start === arr.length) {
for (let i = 0; i < arr.length; i++) {
let pos = i + 1;
let val = arr[i];
if (val % pos !== 0 && pos % val !== 0) return;
}
count++;
return;
}
for (let i = start; i < arr.length; i++) {
[arr[i], arr[start]] = [arr[start], arr[i]];
permute(arr, start + 1);
[arr[i], arr[start]] = [arr[start], arr[i]];
}
}
permute(nums, 0);
return count;
}
// Test
console.log(countArrangement(2)); // Output: 2for (let i = 1; i <= n; i++) nums.push(i);Initialize array with numbers 1 to nif (start === arr.length)Base case: all positions fixed, check validityif (val % pos !== 0 && pos % val !== 0) return;Check divisibility condition for each position[arr[i], arr[start]] = [arr[start], arr[i]];Swap elements to generate permutationscount++Increment count if current permutation is beautifulO(n! * n)O(n)Generating all permutations takes O(n!) time, and checking each permutation takes O(n) time, resulting in O(n! * n) total time. Space is O(n) for recursion stack and permutation storage.
This approach times out for larger inputs because it tries all permutations without pruning, demonstrating the need for optimization.
Intuition
Build the arrangement position by position, only placing numbers that satisfy the divisibility condition at the current position, and skipping invalid choices immediately.
Algorithm
- Start from position 1 and try placing each unused number that satisfies the divisibility condition.
- Mark the chosen number as used and recurse to the next position.
- If all positions are filled, increment the count.
- Backtrack by unmarking the number and trying other candidates.
def countArrangement(n: int) -> int:
count = 0
used = [False] * (n + 1)
def backtrack(pos):
nonlocal count
if pos > n:
count += 1
return
for num in range(1, n + 1):
if not used[num] and (num % pos == 0 or pos % num == 0):
used[num] = True
backtrack(pos + 1)
used[num] = False
backtrack(1)
return count
# Driver code
if __name__ == '__main__':
print(countArrangement(2)) # Output: 2used = [False] * (n + 1)Track which numbers have been used to avoid duplicatesif pos > n:Base case: all positions assigned, increment countif not used[num] and (num % pos == 0 or pos % num == 0)Prune by only choosing numbers that satisfy divisibility conditionused[num] = FalseBacktrack by unmarking the number for other branchespublic class BeautifulArrangement {
private static int count = 0;
public static int countArrangement(int n) {
boolean[] used = new boolean[n + 1];
backtrack(1, n, used);
return count;
}
private static void backtrack(int pos, int n, boolean[] used) {
if (pos > n) {
count++;
return;
}
for (int num = 1; num <= n; num++) {
if (!used[num] && (num % pos == 0 || pos % num == 0)) {
used[num] = true;
backtrack(pos + 1, n, used);
used[num] = false;
}
}
}
public static void main(String[] args) {
System.out.println(countArrangement(2)); // Output: 2
}
}boolean[] used = new boolean[n + 1];Boolean array to track used numbersif (pos > n)Base case: all positions assigned, increment countif (!used[num] && (num % pos == 0 || pos % num == 0))Prune invalid choices earlyused[num] = false;Backtrack to explore other permutations#include <iostream>
#include <vector>
using namespace std;
int count = 0;
void backtrack(int pos, int n, vector<bool>& used) {
if (pos > n) {
count++;
return;
}
for (int num = 1; num <= n; num++) {
if (!used[num] && (num % pos == 0 || pos % num == 0)) {
used[num] = true;
backtrack(pos + 1, n, used);
used[num] = false;
}
}
}
int main() {
int n = 2;
vector<bool> used(n + 1, false);
backtrack(1, n, used);
cout << count << endl; // Output: 2
return 0;
}vector<bool> used(n + 1, false);Track used numbers to avoid repeatsif (pos > n)Base case: all positions assigned, increment countif (!used[num] && (num % pos == 0 || pos % num == 0))Prune invalid candidates earlyused[num] = false;Backtrack to try other numbersfunction countArrangement(n) {
let count = 0;
const used = new Array(n + 1).fill(false);
function backtrack(pos) {
if (pos > n) {
count++;
return;
}
for (let num = 1; num <= n; num++) {
if (!used[num] && (num % pos === 0 || pos % num === 0)) {
used[num] = true;
backtrack(pos + 1);
used[num] = false;
}
}
}
backtrack(1);
return count;
}
// Test
console.log(countArrangement(2)); // Output: 2const used = new Array(n + 1).fill(false);Boolean array to track used numbersif (pos > n)Base case: all positions assigned, increment countif (!used[num] && (num % pos === 0 || pos % num === 0))Prune invalid choices earlyused[num] = false;Backtrack to explore other permutationsO(k!) where k ≤ n, pruned by divisibilityO(n) for recursion and used arrayBacktracking prunes many invalid permutations early, reducing the search space significantly compared to brute force. Exact complexity depends on pruning effectiveness.
This approach is efficient enough for the problem constraints and is the standard solution in interviews.
Intuition
Use an integer bitmask to represent which numbers are used, allowing O(1) checks and updates, while applying the same pruning logic as before.
Algorithm
- Represent used numbers as bits in an integer mask.
- At each position, iterate over numbers 1 to n, checking if the bit is unset and divisibility condition holds.
- Set the bit for chosen number and recurse to next position.
- Unset the bit on backtracking.
def countArrangement(n: int) -> int:
count = 0
def backtrack(pos, used):
nonlocal count
if pos > n:
count += 1
return
for num in range(1, n + 1):
bit = 1 << (num - 1)
if (used & bit) == 0 and (num % pos == 0 or pos % num == 0):
backtrack(pos + 1, used | bit)
backtrack(1, 0)
return count
# Driver code
if __name__ == '__main__':
print(countArrangement(2)) # Output: 2def backtrack(pos, used):Pass current position and bitmask of used numbersbit = 1 << (num - 1)Create bitmask for current number with correct bit positionif (used & bit) == 0 and (num % pos == 0 or pos % num == 0)Check if number unused and satisfies divisibilitybacktrack(pos + 1, used | bit)Recurse with updated bitmask including current numberpublic class BeautifulArrangement {
private static int count = 0;
public static int countArrangement(int n) {
backtrack(1, 0, n);
return count;
}
private static void backtrack(int pos, int used, int n) {
if (pos > n) {
count++;
return;
}
for (int num = 1; num <= n; num++) {
int bit = 1 << (num - 1);
if ((used & bit) == 0 && (num % pos == 0 || pos % num == 0)) {
backtrack(pos + 1, used | bit, n);
}
}
}
public static void main(String[] args) {
System.out.println(countArrangement(2)); // Output: 2
}
}private static void backtrack(int pos, int used, int n)Pass position and bitmask of used numbersint bit = 1 << (num - 1);Create bitmask for current number with correct bit positionif ((used & bit) == 0 && (num % pos == 0 || pos % num == 0))Check if number unused and satisfies divisibilitybacktrack(pos + 1, used | bit, n);Recurse with updated bitmask including current number#include <iostream>
using namespace std;
int count = 0;
void backtrack(int pos, int used, int n) {
if (pos > n) {
count++;
return;
}
for (int num = 1; num <= n; num++) {
int bit = 1 << (num - 1);
if ((used & bit) == 0 && (num % pos == 0 || pos % num == 0)) {
backtrack(pos + 1, used | bit, n);
}
}
}
int main() {
int n = 2;
backtrack(1, 0, n);
cout << count << endl; // Output: 2
return 0;
}void backtrack(int pos, int used, int n)Pass position and bitmask of used numbersint bit = 1 << (num - 1);Create bitmask for current number with correct bit positionif ((used & bit) == 0 && (num % pos == 0 || pos % num == 0))Check if number unused and satisfies divisibilitybacktrack(pos + 1, used | bit, n);Recurse with updated bitmask including current numberfunction countArrangement(n) {
let count = 0;
function backtrack(pos, used) {
if (pos > n) {
count++;
return;
}
for (let num = 1; num <= n; num++) {
let bit = 1 << (num - 1);
if ((used & bit) === 0 && (num % pos === 0 || pos % num === 0)) {
backtrack(pos + 1, used | bit);
}
}
}
backtrack(1, 0);
return count;
}
// Test
console.log(countArrangement(2)); // Output: 2function backtrack(pos, used)Pass current position and bitmask of used numberslet bit = 1 << (num - 1);Create bitmask for current number with correct bit positionif ((used & bit) === 0 && (num % pos === 0 || pos % num === 0))Check if number unused and satisfies divisibilitybacktrack(pos + 1, used | bit);Recurse with updated bitmask including current numberO(k!) with pruning, similar to previous approachO(n) recursion stack, O(1) extra for bitmaskBitmasking reduces memory overhead and can speed up used checks, but overall complexity remains dominated by backtracking search space.
This approach is a neat optimization that can impress interviewers and is practical for larger n within constraints.
| Approach | Time | Space | Stack Risk | Reconstruct | Use In Interview |
|---|---|---|---|---|---|
| 1. Brute Force | O(n! * n) | O(n) | Yes (deep recursion) | Yes | Mention only - never code |
| 2. Backtracking with Pruning | O(k!) with pruning (k ≤ n) | O(n) | Yes (manageable for n ≤ 15) | Yes | Code this approach |
| 3. Backtracking with Bitmask Optimization | O(k!) with pruning | O(n) recursion, O(1) extra | Yes | Yes | Optional optimization if time permits |
How to Present
Step 1: Clarify the problem and constraints with the interviewer.Step 2: Describe the brute force approach to generate all permutations and check conditions.Step 3: Explain why brute force is inefficient and introduce backtracking with pruning.Step 4: Discuss further optimization using bitmasking for used number tracking.Step 5: Code the backtracking with pruning approach and test with examples.
Time Allocation
What the Interviewer Tests
The interviewer tests your understanding of backtracking, pruning to reduce search space, and ability to implement recursion with state management correctly.
Common Follow-ups
- What if n is very large? → Discuss time complexity and pruning limits.
- Can you generate all beautiful arrangements instead of counting? → Modify code to store permutations.
When to Use
1) Problem involves permutations or arrangements. 2) Constraints depend on position and element values. 3) Need to count or generate valid sequences. 4) Divisibility or other pruning conditions apply.
