Count of permutations of an Array having each element as a multiple or a factor of its index
Last Updated :
16 Dec, 2024
Given an integer n, the task is to count the number of ways to generate an array, arr[] of consisting of n integers such that for every index i (1-based indexing), arr[i] is either a factor or a multiple of i, or both.
Note: The arr[] must be the permutations of all the numbers from the range [1, n].
Examples:
Input: n = 2
Output: 2
Explanation: Two possible arrangements are {1, 2} and {2, 1}
Input: n = 3
Output: 3
Explanation: The 6 possible arrangements are {1, 2, 3}, {2, 1, 3}, {3, 2, 1}, {3, 1, 2}, {2, 3, 1} and {1, 3, 2}. Among them, the valid arrangements are {1, 2, 3}, {2, 1, 3} and {3, 2, 1}.
Using Recursion - O(n!) Time and O(n) Space
The idea is to recursively generate all valid permutations of numbers from 1 to n such that the number at each index is divisible by or divides the index. We maintain an array arr[] to track the current permutation and a boolean array check[] to ensure no number is used more than once. The base case is when all positions are filled, at which point the count of valid permutations is incremented. For each recursive step, we try each unused number that satisfies the divisibility condition and backtrack to explore other possibilities.
C++
// C++ recursive approach to count valid permutations
#include <bits/stdc++.h>
using namespace std;
// Helper function to count valid permutations recursively
void validPermutationsHelper(vector<int> &arr,
vector<bool> &check, int idx,
int &count, int n) {
// Base case: If all positions are
// filled, increment count
if (idx > n) {
count++;
return;
}
for (int i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check[i] && (i % idx == 0
|| idx % i == 0)) {
// Mark the number as used and proceed to
// the next index
check[i] = true;
arr[idx - 1] = i;
validPermutationsHelper(arr, check,
idx + 1, count, n);
// Backtrack: Unmark the number and
// reset the index
check[i] = false;
}
}
}
// Wrapper function for recursion
int validPermutations(int n) {
vector<int> arr(n);
// Boolean array to keep track of used numbers
vector<bool> check(n + 1, false);
int count = 0;
// Start recursion from the first index
validPermutationsHelper(arr, check, 1, count, n);
return count;
}
int main() {
int n = 3;
int result = validPermutations(n);
cout << result;
return 0;
}
Java
// Java recursive approach to count valid permutations
import java.util.*;
class GfG {
// Helper function to count valid permutations
static void validPermutationsHelper(List<Integer> arr,
List<Boolean> check,
int idx,
int[] count,
int n) {
// Base case: If all positions are
// filled, increment count
if (idx > n) {
count[0]++;
return;
}
for (int i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check.get(i) && (i % idx == 0
|| idx % i == 0)) {
// Mark the number as used and proceed to
// the next index
check.set(i, true);
arr.set(idx - 1, i);
validPermutationsHelper(arr, check,
idx + 1,
count, n);
// Backtrack: Unmark the number and
// reset the index
check.set(i, false);
}
}
}
// Wrapper function for recursion
static int validPermutations(int n) {
List<Integer> arr
= new ArrayList<>(Collections.nCopies(n, 0));
// Boolean list to keep track of used numbers
List<Boolean> check
= new ArrayList<>(Collections.nCopies(n + 1, false));
int[] count = {0};
// Start recursion from the first index
validPermutationsHelper(arr, check, 1, count, n);
return count[0];
}
public static void main(String[] args) {
int n = 3;
int result = validPermutations(n);
System.out.println(result);
}
}
Python
# Python recursive approach to count valid permutations
def validPermutationsHelper(arr, check, idx, count, n):
# Base case: If all positions are filled,
# increment count
if idx > n:
count[0] += 1
return
for i in range(1, n + 1):
# Check if the number is unused and valid
# for the current index
if not check[i] and (i % idx == 0 or idx % i == 0):
# Mark the number as used and proceed
# to the next index
check[i] = True
arr[idx - 1] = i
validPermutationsHelper(arr, check, idx + 1, count, n)
# Backtrack: Unmark the number and reset the index
check[i] = False
def validPermutations(n):
arr = [0] * n
# Boolean list to keep track of used numbers
check = [False] * (n + 1)
count = [0]
# Start recursion from the first index
validPermutationsHelper(arr, check, 1, count, n)
return count[0]
if __name__ == "__main__":
n = 3
result = validPermutations(n)
print(result)
C#
// C# recursive approach to count valid permutations
using System;
using System.Collections.Generic;
class GfG {
// Helper function to count valid permutations
static void ValidPermutationsHelper(List<int> arr,
List<bool> check,
int idx,
int[] count,
int n) {
// Base case: If all positions are
// filled, increment count
if (idx > n) {
count[0]++;
return;
}
for (int i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check[i] && (i % idx == 0
|| idx % i == 0)) {
// Mark the number as used and proceed to
// the next index
check[i] = true;
arr[idx - 1] = i;
ValidPermutationsHelper(arr, check,
idx + 1,
count, n);
// Backtrack: Unmark the number and
// reset the index
check[i] = false;
}
}
}
// Wrapper function for recursion
static int ValidPermutations(int n) {
List<int> arr
= new List<int>(new int[n]);
// Boolean list to keep track of used numbers
List<bool> check
= new List<bool>(new bool[n + 1]);
int[] count = { 0 };
// Start recursion from the first index
ValidPermutationsHelper(arr, check, 1, count, n);
return count[0];
}
static void Main(string[] args) {
int n = 3;
int result = ValidPermutations(n);
Console.WriteLine(result);
}
}
JavaScript
// JavaScript recursive approach to count
// valid permutations
function validPermutationsHelper(arr, check,
idx, count, n) {
// Base case: If all positions are filled,
// increment count
if (idx > n) {
count[0]++;
return;
}
for (let i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check[i] && (i % idx === 0 || idx % i === 0)) {
// Mark the number as used and proceed
// to the next index
check[i] = true;
arr[idx - 1] = i;
validPermutationsHelper(arr, check,
idx + 1, count, n);
// Backtrack: Unmark the number and
// reset the index
check[i] = false;
}
}
}
function validPermutations(n) {
const arr = new Array(n).fill(0);
// Boolean array to track used numbers
const check = new Array(n + 1).fill(false);
const count = [0];
// Start recursion from the first index
validPermutationsHelper(arr, check, 1, count, n);
return count[0];
}
const n = 3;
console.log(validPermutations(n));
Time Complexity: O(n!), due to generating all permutations and checking the divisibility condition.
Auxiliary Space: O(n), for the recursion stack.
Using Dynamic Programming - O(n*2^n) Time and O(n*2^n) Space
The idea is to count all valid permutations of numbers from 1 to n such that for each index i, the number placed at that index must either divide i or be divisible by i. To optimize the process, memoization is used to store intermediate results based on the current index and the bitmask representing the used numbers. The recursion explores all valid numbers at each index and backtracks to find all possible permutations. The bitmask efficiently tracks which numbers have been used, and the memoization table prevents redundant recalculations, significantly improving the performance.
C++
// C++ recursive approach to count valid permutations with
// memoization
#include <bits/stdc++.h>
using namespace std;
// Helper function to count valid permutations recursively
int validPermutationsHelper(vector<int> &arr,
vector<bool> &check,
int idx,
vector<vector<int>> &memo,
int n) {
// Base case: If all positions are filled, return 1
if (idx > n) {
return 1;
}
// Convert the used vector into a bitmask
int usedMask = 0;
for (int i = 1; i <= n; i++) {
if (check[i]) {
// Set the corresponding bit
usedMask |= (1 << i);
}
}
// Check if the result is already computed
if (memo[idx][usedMask] != -1) {
return memo[idx][usedMask];
}
int count = 0;
for (int i = 1; i <= n; i++) {
// Check if the number is unused and valid for the
// current index
if (!check[i] && (i % idx == 0 || idx % i == 0)) {
// Mark the number as used and proceed to the next index
check[i] = true;
arr[idx - 1] = i;
// Recur for the next index
count += validPermutationsHelper(arr, check,
idx + 1, memo, n);
// Backtrack: Unmark the number
check[i] = false;
}
}
// Memoize the result for this subproblem
memo[idx][usedMask] = count;
return count;
}
// Wrapper function for recursion
int validPermutations(int n) {
vector<int> arr(n);
// Boolean array to keep track of used numbers
vector<bool> check(n + 1, false);
// Create memoization table initialized to -1
vector<vector<int>> memo(n + 1,
vector<int>(1 << (n + 1), -1));
// Start recursion from the first index
return validPermutationsHelper(arr,
check, 1, memo, n);
}
int main() {
int n = 3;
int result = validPermutations(n);
cout << result;
return 0;
}
Java
// Java recursive approach to count valid permutations
// with memoization
import java.util.*;
class GfG {
// Helper function to count valid permutations
// recursively with memoization
static int validPermutationsHelper(List<Integer> arr,
List<Boolean> check,
int idx,
List<List<Integer>> memo,
int n) {
// Base case: If all positions are filled, return 1
if (idx > n) {
return 1;
}
// Convert the used list into a bitmask
int usedMask = 0;
for (int i = 1; i <= n; i++) {
if (check.get(i)) {
usedMask |= (1 << i);
}
}
// Check if the result is already computed
if (memo.get(idx).get(usedMask) != -1) {
return memo.get(idx).get(usedMask);
}
int count = 0;
for (int i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check.get(i) && (i % idx == 0
|| idx % i == 0)) {
// Mark the number as used and proceed
// to the next index
check.set(i, true);
arr.set(idx - 1, i);
// Recur for the next index
count += validPermutationsHelper(arr,
check, idx + 1, memo, n);
// Backtrack: Unmark the number
check.set(i, false);
}
}
// Memoize the result for this subproblem
memo.get(idx).set(usedMask, count);
return count;
}
// Wrapper function for recursion
static int validPermutations(int n) {
List<Integer> arr
= new ArrayList<>(Collections.nCopies(n, 0));
// Boolean list to keep track of used numbers
List<Boolean> check
= new ArrayList<>(Collections.nCopies(n + 1, false));
// Create memoization table initialized to -1
List<List<Integer>> memo = new ArrayList<>();
for (int i = 0; i <= n; i++) {
memo.add(new ArrayList<>(Collections.nCopies(1 << (n + 1), -1)));
}
// Start recursion from the first index
return validPermutationsHelper(arr, check, 1, memo, n);
}
public static void main(String[] args) {
int n = 3;
int result = validPermutations(n);
System.out.println(result);
}
}
Python
# Python recursive approach to count valid permutations
# with memoization
# Helper function to count valid permutations
# recursively with memoization
def ValidPermutationsHelper(arr, check, idx, memo, n):
# Base case: If all positions are filled, return 1
if idx > n:
return 1
# Convert the used list into a bitmask
usedMask = 0
for i in range(1, n + 1):
if check[i]:
usedMask |= (1 << i)
# Check if the result is already computed
if memo[idx][usedMask] != -1:
return memo[idx][usedMask]
count = 0
for i in range(1, n + 1):
# Check if the number is unused and valid
# for the current index
if not check[i] and (i % idx == 0 or idx % i == 0):
# Mark the number as used and proceed
# to the next index
check[i] = True
arr[idx - 1] = i
# Recur for the next index
count += ValidPermutationsHelper(arr, \
check, idx + 1, memo, n)
# Backtrack: Unmark the number
check[i] = False
# Memoize the result for this subproblem
memo[idx][usedMask] = count
return count
# Wrapper function for recursion
def ValidPermutations(n):
arr = [0] * n
# Boolean list to keep track of used numbers
check = [False] * (n + 1)
# Create memoization table initialized to -1
memo = []
for i in range(n + 1):
memo.append([-1] * (1 << (n + 1)))
# Start recursion from the first index
return ValidPermutationsHelper(arr, check, 1, memo, n)
if __name__ == "__main__":
n = 3
result = ValidPermutations(n)
print(result)
C#
// C# recursive approach to count valid permutations
// with memoization
using System;
using System.Collections.Generic;
class GfG {
// Helper function to count valid permutations
// recursively with memoization
static int ValidPermutationsHelper(List<int> arr,
List<bool> check,
int idx,
List<List<int>> memo,
int n) {
// Base case: If all positions are filled, return 1
if (idx > n) {
return 1;
}
// Convert the used list into a bitmask
int usedMask = 0;
for (int i = 1; i <= n; i++) {
if (check[i]) {
usedMask |= (1 << i);
}
}
// Check if the result is already computed
if (memo[idx][usedMask] != -1) {
return memo[idx][usedMask];
}
int count = 0;
for (int i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check[i] && (i % idx == 0
|| idx % i == 0)) {
// Mark the number as used and proceed
// to the next index
check[i] = true;
arr[idx - 1] = i;
// Recur for the next index
count += ValidPermutationsHelper(arr,
check, idx + 1, memo, n);
// Backtrack: Unmark the number
check[i] = false;
}
}
// Memoize the result for this subproblem
memo[idx][usedMask] = count;
return count;
}
// Wrapper function for recursion
static int ValidPermutations(int n) {
List<int> arr = new List<int>(new int[n]);
// Boolean list to keep track of used numbers
List<bool> check = new List<bool>(new bool[n + 1]);
// Create memoization table initialized to -1
List<List<int>> memo = new List<List<int>>();
for (int i = 0; i <= n; i++) {
memo.Add(new List<int>(new int[1 << (n + 1)]));
for (int j = 0; j < memo[i].Count; j++) {
memo[i][j] = -1;
}
}
// Start recursion from the first index
return ValidPermutationsHelper(arr, check, 1, memo, n);
}
public static void Main(string[] args) {
int n = 3;
int result = ValidPermutations(n);
Console.WriteLine(result);
}
}
JavaScript
// JavaScript recursive approach to count valid permutations
// with memoization
// Helper function to count valid permutations
// recursively with memoization
function ValidPermutationsHelper(arr, check, idx, memo, n) {
// Base case: If all positions are filled, return 1
if (idx > n) {
return 1;
}
// Convert the used list into a bitmask
let usedMask = 0;
for (let i = 1; i <= n; i++) {
if (check[i]) {
usedMask |= (1 << i);
}
}
// Check if the result is already computed
if (memo[idx][usedMask] !== -1) {
return memo[idx][usedMask];
}
let count = 0;
for (let i = 1; i <= n; i++) {
// Check if the number is unused and valid
// for the current index
if (!check[i] && (i % idx === 0 || idx % i === 0)) {
// Mark the number as used and proceed
// to the next index
check[i] = true;
arr[idx - 1] = i;
// Recur for the next index
count += ValidPermutationsHelper(arr, check, idx + 1, memo, n);
// Backtrack: Unmark the number
check[i] = false;
}
}
// Memoize the result for this subproblem
memo[idx][usedMask] = count;
return count;
}
// Wrapper function for recursion
function ValidPermutations(n) {
let arr = new Array(n).fill(0);
// Boolean list to keep track of used numbers
let check = new Array(n + 1).fill(false);
// Create memoization table initialized to -1
let memo = [];
for (let i = 0; i <= n; i++) {
memo.push(new Array(1 << (n + 1)).fill(-1));
}
// Start recursion from the first index
return ValidPermutationsHelper(arr, check, 1, memo, n);
}
const n = 3;
console.log(ValidPermutations(n));
Time Complexity: O(n * 2^n), due to recursion and memoization.
Auxiliary Space: O(n * 2^n), for the memoization table and recursion stack.
Similar Reads
DSA Tutorial - Learn Data Structures and Algorithms DSA (Data Structures and Algorithms) is the study of organizing data efficiently using data structures like arrays, stacks, and trees, paired with step-by-step procedures (or algorithms) to solve problems effectively. Data structures manage how data is stored and accessed, while algorithms focus on
7 min read
Quick Sort QuickSort is a sorting algorithm based on the Divide and Conquer that picks an element as a pivot and partitions the given array around the picked pivot by placing the pivot in its correct position in the sorted array. It works on the principle of divide and conquer, breaking down the problem into s
12 min read
Merge Sort - Data Structure and Algorithms Tutorials Merge sort is a popular sorting algorithm known for its efficiency and stability. It follows the divide-and-conquer approach. It works by recursively dividing the input array into two halves, recursively sorting the two halves and finally merging them back together to obtain the sorted array. Merge
14 min read
Bubble Sort Algorithm Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in the wrong order. This algorithm is not suitable for large data sets as its average and worst-case time complexity are quite high.We sort the array using multiple passes. After the fir
8 min read
Data Structures Tutorial Data structures are the fundamental building blocks of computer programming. They define how data is organized, stored, and manipulated within a program. Understanding data structures is very important for developing efficient and effective algorithms. What is Data Structure?A data structure is a st
2 min read
Breadth First Search or BFS for a Graph Given a undirected graph represented by an adjacency list adj, where each adj[i] represents the list of vertices connected to vertex i. Perform a Breadth First Search (BFS) traversal starting from vertex 0, visiting vertices from left to right according to the adjacency list, and return a list conta
15+ min read
Binary Search Algorithm - Iterative and Recursive Implementation Binary Search Algorithm is a searching algorithm used in a sorted array by repeatedly dividing the search interval in half. The idea of binary search is to use the information that the array is sorted and reduce the time complexity to O(log N). Binary Search AlgorithmConditions to apply Binary Searc
15 min read
Insertion Sort Algorithm Insertion sort is a simple sorting algorithm that works by iteratively inserting each element of an unsorted list into its correct position in a sorted portion of the list. It is like sorting playing cards in your hands. You split the cards into two groups: the sorted cards and the unsorted cards. T
9 min read
Dijkstra's Algorithm to find Shortest Paths from a Source to all Given a weighted undirected graph represented as an edge list and a source vertex src, find the shortest path distances from the source vertex to all other vertices in the graph. The graph contains V vertices, numbered from 0 to V - 1.Note: The given graph does not contain any negative edge. Example
12 min read
Selection Sort Selection Sort is a comparison-based sorting algorithm. It sorts an array by repeatedly selecting the smallest (or largest) element from the unsorted portion and swapping it with the first unsorted element. This process continues until the entire array is sorted.First we find the smallest element an
8 min read