Beautiful Arrangement

Updated on 14 May, 2025
Beautiful Arrangement header image

Problem Statement

Suppose you are given a set of integers ranging from 1 to n. Your goal is to find all permutations of this integer set such that the permutation meets a specific criterion to qualify as a "beautiful arrangement". A permutation is only considered beautiful if for every position i in the permutation, at least one of the following conditions is met:

  • The integer at the i-th position of the permutation, denoted as perm[i], is divisible by i.
  • The position i itself is divisible by the integer perm[i].

The task is to compute the total number of such beautiful arrangements that can be obtained for a given integer n.

Examples

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

Approach and Intuition

The core of solving this problem lies in understanding the properties of divisibility and permutations. Our approach will involve exploring all permutations to count how many meet the beautiful arrangement criteria. Here's how we can intuitively think about solving this:

  1. Brute Force with Optimization: We could use a backtracking algorithm which tries to build each permutation step by step.

    • Start by placing each number in each position and check if placing the number in the current position keeps the arrangement beautiful.
    • If the position division property holds (either position divides the number or the number divides the position), continue to the next position. Otherwise, backtrack.
  2. Pruning using Constraints:

    • If at any point a condition is violated, we can stop further exploration down that path. This pruning helps in reducing the number of permutations we need to explore.
  3. Caching Results (Memoization):

    • For bigger values of n, it might be beneficial to remember results for smaller sub-problems. This can significantly speed up the solving process as it prevents redundant calculations.
  4. Using Bit Manipulation:

    • We can represent the permutation as a number in binary format. This will help in quickly checking which elements have been used in the permutation so far.

This problem is a good example of using combinatorial methods coupled with algorithmic optimizations such as backtracking and bit manipulation to handle permutations and divisibility checks efficiently. Given the constraints of n being at most 15, such an approach should be computationally feasible.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    int countArrangement(int num) {
        vector<bool> marked(num + 1, false);
        arrange(num, 1, marked);
        return arrangements;
    }

private:
    int arrangements = 0;
    void arrange(int num, int position, vector<bool>& marked) {
        if (position > num)
            arrangements++;
        else
            for (int idx = 1; idx <= num; ++idx)
                if (!marked[idx] && (position % idx == 0 || idx % position == 0)) {
                    marked[idx] = true;
                    arrange(num, position + 1, marked);
                    marked[idx] = false;
                }
    }
};

The solution for the "Beautiful Arrangement" problem in C++ involves implementing a backtracking mechanism to count all the possible permutations of numbers that meet specific divisibility conditions. The function countArrangement initiates this process by creating a boolean vector marked to keep track of the numbers that have been used in the permutation so far.

Here is a brief breakdown of the steps followed in the code:

  1. Initiate a vector marked sized num + 1 and filled with false, indicating that no numbers are initially marked.
  2. Call the recursive helper function arrange starting from position 1.
  3. The arrange function applies backtracking to explore each possible positioning of numbers:
    • If the current position exceeds num, increment the count of valid arrangements.
    • Loop over each number from 1 to num. If a number is not marked and satisfies the divisibility conditions (position % idx == 0 || idx % position == 0), mark the number and move to the next position by making a recursive call to arrange.
    • After the recursive call, unmark the number to backtrack and try the next possibility.

Throughout the recursive process, the global variable arrangements accumulates the count of all valid permutations that satisfy the specified conditions. Once all possibilities are explored, the countArrangement function returns the total count of these beautiful arrangements.

java
public class Solution {

    int arrangementCounter = 0;

    public int countArrangement(int n) {
        boolean[] hasVisited = new boolean[n + 1];
        permute(n, 1, hasVisited);
        return arrangementCounter;
    }

    public void permute(int n, int pos, boolean[] hasVisited) {
        if (pos > n) {
            arrangementCounter++;
            return;
        }

        for (int i = 1; i <= n; i++) {
            if (!hasVisited[i] && (pos % i == 0 || i % pos == 0)) {
                hasVisited[i] = true;
                permute(n, pos + 1, hasVisited);
                hasVisited[i] = false;
            }
        }
    }
}

This Java solution tackles the problem of finding the total number of "Beautiful Arrangements" possible. Here, a beautiful arrangement is defined such that for every position in the permutation of numbers from 1 to n, the number at that position is divisible by its positional index or vice-versa.

The solution uses a backtracking approach:

  • A helper method permute is used to generate permutations recursively.
  • This method checks each number from 1 to n to see if it hasn't been placed in the permutation (checked by hasVisited array) and can be placed at the current position (position pos) according to the divisibility condition (either pos is divisible by the number or the number is divisible by pos).
  • If the conditions are met, the number is marked as visited, and the function recursively proceeds to the next position.
  • If it reaches a position beyond n (pos > n), it recognizes a valid arrangement and increments the arrangementCounter.
  • After exploring all possibilities for a number at a position, it backtracks by marking the number as unvisited (hasVisited[i] = false) and proceeds to try the next number.

In the end, countArrangement initializes the needed variables and starts the recursive process by calling permute, then returns the total count of beautiful arrangements found.

This recursive method efficiently explores all potential combinations meeting the specific conditions, using backtracking to count all valid permutations, directly implementing the logic to adhere strictly to the problem's constraints.

python
class Solution:
    def __init__(self):
        self.total = 0

    def findArrangement(self, M):
        seen = [False] * (M + 1)
        self.process(M, 1, seen)
        return self.total

    def process(self, M, current_position, seen):
        if current_position > M:
            self.total += 1
            return
        for x in range(1, M + 1):
            if not seen[x] and (current_position % x == 0 or x % current_position == 0):
                seen[x] = True
                self.process(M, current_position + 1, seen)
                seen[x] = False

The given Python solution involves finding the number of arrangements possible where the position and the number at that position meet certain divisibility conditions—an essential aspect of determining "Beautiful Arrangements." The provided code defines a class Solution that employs backtracking to explore all possible arrangements and tally those meeting the criteria.

  • Initialize an instance variable total in the constructor to count valid arrangements.
  • Use findArrangement method to initiate the process with:
    • An array seen to track used numbers.
    • A recursive helper method process.
  • process method dives into recursive backtracking:
    • It increments total when a valid arrangement is found (all positions are filled).
    • For each number not seen yet, it checks divisibility conditions with the current position. If valid, it marks the number as seen and proceeds recursively to the next position before backtracking.

Execute the following steps to use the Solution class effectively:

  1. Instantiate Solution.
  2. Call findArrangement with the desired number of elements M.
  3. Retrieve the total count of valid arrangements from the total attribute after execution.

The approach ensures exploration of all potential combinations, leveraging the efficiency and clarity of recursion and backtracking for counting arrangements that satisfy specific constraints linked to indices and their respective values.

Comments

No comments yet.