Number of Ways to Reorder Array to Get Same BST

Updated on 11 July, 2025
Number of Ways to Reorder Array to Get Same BST header image

Problem Statement

Given an array nums which represents a unique permutation of integers ranging from 1 to n, the challenge is to construct a binary search tree (BST) by sequentially inserting the elements from nums into a newly initialized BST. The task is to figure out the number of distinct reorderings of the array nums that would result in the exact same BST structure as the one originally formed with the provided order in nums.

Key details:

  • The BST is constructed by inserting elements in the given order.
  • Only reorderings that lead to the identical BST structure are counted.
  • Due to potential large outputs, results should be returned modulo 10^9 + 7.

The problem provides illustrative examples where, for instance, both sequences [2, 1, 3] and [2, 3, 1] establish identical BSTs but differ from sequences like [3, 2, 1] which form a different BST structure. This highlights the need to comprehend how sequential insertions structure the tree and how its configuration restricts or allows certain permutations.

Examples

Example 1

Input:

nums = [2,1,3]

Output:

1

Explanation:

We can reorder nums to be [2,3,1] which will yield the same BST. There are no other ways to reorder nums which will yield the same BST.

Example 2

Input:

nums = [3,4,5,1,2]

Output:

5

Explanation:

The following 5 arrays will yield the same BST:
[3,1,2,4,5]
[3,1,4,2,5]
[3,1,4,5,2]
[3,4,1,2,5]
[3,4,1,5,2]

Example 3

Input:

nums = [1,2,3]

Output:

0

Explanation:

There are no other orderings of nums that will yield the same BST.

Constraints

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= nums.length
  • All integers in nums are distinct.

Approach and Intuition

Constructing the BST from nums and then understanding the structure of this tree gives the basis for determining possible reorderings that yield the same BST. The approach and intuition can be grasped by closely navigating through the examples provided.

Approach:

  1. Identify the Root:

    • In any BST formed from a permutation, the first number inserted is always the root.
    • For a sequence to yield the same BST, the root must remain the same.
  2. Divide Into Subtrees:

    • Separate the remaining elements into:

      • Left subtree: all elements smaller than the root.
      • Right subtree: all elements larger than the root.
  3. Recursive Structure:

    • Recursively apply the same logic to left and right subtrees.
    • Count how many reorderings within each subtree produce the same structure.
    • Combine the subtree counts using combinatorics.
  4. Combinatorial Counting:

    • Suppose the left subtree has L nodes and the right has R nodes.
    • The number of ways to merge these two subtrees preserving internal order is: C(L + R, L) (binomial coefficient).
    • Total reorderings = ways(left subtree) * ways(right subtree) * C(L + R, L)
  5. Memoization:

    • Use memoization or caching to avoid recomputation for duplicate subtree structures.
  6. Final Adjustment:

    • Subtract 1 at the end to exclude the original input order itself if it's required to count only other permutations.

Intuition:

Each number in nums belongs to a specific place in the BST based on relative ordering. Once the tree structure is fixed:

  • You can reorder the left and right subtree nodes among themselves.
  • But the relative subtree positioning must remain intact.
  • The total number of valid reorderings is the number of ways you can interleave left and right subtrees while maintaining their internal order.

The final count must be returned modulo 10^9 + 7 due to large possible outputs. This problem, at its core, is a combinatorics and divide-and-conquer application centered around preserving structure in recursive tree formations.

Solutions

  • C++
cpp
class Solution {
public:
    int countWays(vector<int>& nums) {
        int n = nums.size();
        // Generate Pascal's Triangle for nCk calculation
        pascalTriangle.resize(n + 1);
        for(int i = 0; i <= n; ++i) {
            pascalTriangle[i] = vector<long long>(i + 1, 1);
            for(int j = 1; j < i; ++j) {
                pascalTriangle[i][j] = (pascalTriangle[i - 1][j - 1] + pascalTriangle[i - 1][j]) % MODULO;
            }
        }
            
        return (computeWays(nums) - 1) % MODULO;
    }
        
private:
    vector<vector<long long>> pascalTriangle;
    const long long MODULO = 1e9 + 7;
        
    long long computeWays(vector<int> &nums) {
        int n = nums.size();
        if(n <= 2) {
            return 1;
        }
    
        vector<int> left, right;
        for(int i = 1; i < n; ++i) {
            if (nums[i] < nums[0]) {
                left.push_back(nums[i]);
            } else {
                right.push_back(nums[i]);
            }
        }
    		
        long long waysLeft = computeWays(left) % MODULO;
        long long waysRight = computeWays(right) % MODULO;
    		
        return (((waysLeft * waysRight) % MODULO) * pascalTriangle[n - 1][left.size()]) % MODULO;
    }
};

The solution in C++ revolves around finding the number of ways to reorder an array to obtain the same Binary Search Tree (BST) structure. Here is the breakdown of the implemented approach:

  1. Calculate combinations using Pascal's Triangle: Initialization of a matrix (pascalTriangle) to store binomial coefficients, which will help in computing combinations efficiently. This matrix is built dynamically using the properties of Pascal's Triangle for nCk calculation.

  2. Define a primary function countWays to count all possible reorders. This function reduces the result modulo 1e9 + 7 (MODULO) and subtracts one to account for the initial configuration.

  3. Implement a recursive helper function computeWays:

    • It determines the base case when the size of the subarray (nums) is less than or equal to two, in which there is only one arrangement possible.
    • Splits the tree's root elements into left and right subtrees.
    • Recursively calculates the number of ways for left and right subtrees.
    • Uses the modular arithmetic rules to ensure numbers do not overflow and remain within bounds.
  4. The multiplication of the number of ways for left and right subtrees is combined using the respective binomial coefficient from the precomputed pascalTriangle. This multiplication gives the total ways to merge the left and right subtrees while maintaining the original BST structure.

  5. Return the result from the parent function after adjusting it with the MODULO.

This approach efficiently leverages dynamic programming and combinatorial mathematics to solve the problem, ensuring optimal performance on larger datasets.

  • Java
java
class Solution {
    private long modulus = (long)1e9 + 7;
    private long[][] pascalsTriangle;
    
    public int numberOfWays(int[] elements) {
        int n = elements.length;
            
        // Initializing Pascal's triangle.
        pascalsTriangle = new long[n][n];
        for (int i = 0; i < n; ++i) {
            pascalsTriangle[i][0] = pascalsTriangle[i][i] = 1;
        }
        for (int i = 2; i < n; i++) {
            for (int j = 1; j < i; j++) {
                pascalsTriangle[i][j] = (pascalsTriangle[i - 1][j - 1] + pascalsTriangle[i - 1][j]) % modulus;
            }
        }
        List<Integer> elementsList = Arrays.stream(elements).boxed().collect(Collectors.toList());
        return (int)((findWays(elementsList) - 1) % modulus);
    }
    
    private long findWays(List<Integer> elementsList) {
        int n = elementsList.size();
        if (n < 3) {
            return 1;
        }
    
        List<Integer> smaller = new ArrayList<>();
        List<Integer> larger = new ArrayList<>();
        for (int i = 1; i < n; ++i) {
            if (elementsList.get(i) < elementsList.get(0)) {
                smaller.add(elementsList.get(i));
            } else {
                larger.add(elementsList.get(i));
            }
        }
        long waysSmaller = findWays(smaller) % modulus;
        long waysLarger = findWays(larger) % modulus;
    
        return (((waysSmaller * waysLarger) % modulus) * pascalsTriangle[n - 1][smaller.size()]) % modulus;
    }
}

This Java solution tackles the problem of counting the number of ways to reorder an array so that it forms the same Binary Search Tree (BST) by organizing the task into a series of combinatorial steps. The solution relies heavily on combinatorics and the use of Pascal’s triangle to find the combinatorial coefficients efficiently.

The provided code proceeds as follows:

  • Initialize a 2D array named pascalsTriangle to store binomial coefficients which will be used later to compute the number of combinations possible at each step while splitting the tree.
  • In the main function numberOfWays, it first represents the input array as a list of integers, then calls findWays to recursively calculate the number of valid reorderings.
  • In the recursive method findWays, the code segments:
    • Identifies the root of the current subtree (first element of the list).
    • Distributes the remaining elements into smaller and larger lists depending on whether elements are less than or greater than the root, respectively.
    • Computes the number of ways to rearrange both the smaller and larger list recursively.
    • Combines the number of ways to reorder elements in both subtrees using the combinatorial values from pascalsTriangle. This combination considers how many ways the smaller and larger subsets can be interleaved around the root while maintaining the BST property.
  • The computation considers modulo 1e9 + 7 to avoid overflow issues with large numbers.

Overall, this solution breaks down the array based on BST properties and uses dynamic programming with a precomputed Pascal's triangle for efficient combinatorial calculations. This approach allows the solution to handle potentially large arrays by addressing factorial growth using modular arithmetic.

  • Python
python
class Solution:
    def calculateWays(self, numbers: List[int]) -> int:
        modulus = 1000000007
            
        def explore(nums):
            n = len(nums)
            if n <= 2:
                return 1
            left_subtree = [x for x in nums if x < nums[0]]
            right_subtree = [x for x in nums if x > nums[0]]
            return (explore(left_subtree) * explore(right_subtree) * comb(n - 1, len(left_subtree))) % modulus
            
        return (explore(numbers) - 1) % modulus

This summary explains the solution to the problem of calculating the number of ways to reorder an array such that the resulting Binary Search Tree (BST) remains the same as the BST formed by the original array.

The solution is implemented in Python. The main functionality resides within the calculateWays method of the Solution class. The method takes a list of integers numbers as input, representing the array.

  • Inside calculateWays, a nested function explore is defined. It recursively determines the number of ways the subtree arrays can be reordered.
  • The approach involves dividing the input array into two subtrees - the left subtree containing elements less than the root (first element of the array) and the right subtree containing elements greater than the root.
  • Base case for recursion is defined when the length of the list is 2 or less, in which case there is only one way to arrange the elements (either one element or the elements are the same as the BST).
  • The Binomial coefficient, represented by comb(n - 1, len(left_subtree)), calculates the number of ways to choose positions for the left subtree elements in the reordered array.
  • The result from explore function is adjusted with modulo 1000000007 to prevent overflow and ensure the result fits within standard data ranges.

Finally, the total number of ways calculated by explore is reduced by one (explore(numbers) - 1), as it includes the original order of the array itself, which should be excluded according to the problem requirements. The final result is also taken modulo 1000000007 to keep it within the range.

This recursive algorithm efficiently calculates the number of valid reorderings using memoization provided by recursion and combinatorial calculations with the binomial coefficient, ensuring an optimized and effective solution to the problem.

Comments

No comments yet.