Number of Ways to Reorder Array to Get Same BST solution and explanation
9 more videos on this problem
LeetCode 1569. Number of Ways to Reorder Array to Get Same BSTHappy Coding · 25:32 · 8K views
Number of Ways to Reorder Array to Get Same BST II Pattern II Math II Combinatorics II RecursionAryan Mittal · 41:42 · 8K views
Number of Ways to Reorder Array to Get Same BST | Pascal's triangle | GOOGLE | Leetcode-1569codestorywithMIK · 41:30 · 5K views
LeetCode 1569. Number of Ways to Reorder Array to Get Same BSTAbrar Sher · 21:33 · 5K views
1569. Number of Ways to Reorder Array to Get Same BST | Recursion | DFS | LeetCode Daily ChallengeDeep Tech · 22:49 · 3K views
1569. Number of Ways to Reorder Array to Get Same BST (Leetcode Hard)Programming Live with Larry · 21:09 · 2K views
LeetCode 1569 Number of Ways to Reorder Array to Get Same BSTJeevan Kumar - Cracckify · 9:17 · 1K views
Leetcode 1569. Number of Ways to Reorder Array to Get Same BSTFearless Learner · 11:56 · 656 views
Daily Leetcode #1172: Jun 16, 2023 - 1569. Number of Ways to Reorder Array to Get Same BSTProgramming Live with Larry · 22:04 · 420 views
Number of Way to Reorder Array to get Same BST | Leetcode Q.1569 | Python | LeetCode Daily ChallengeCodeRapt · 14:17 · 187 views
The problem
Given an array nums that represents a permutation of integers from 1 to n. We are going to construct a binary search tree (BST) by inserting the elements of nums in order into an initially empty BST. Find the number of different ways to reorder nums so that the constructed BST is identical to that formed from the original array nums.
- For example, given
nums = [2,1,3], we will have 2 as the root, 1 as a left child, and 3 as a right child. The array[2,3,1]also yields the same BST but[3,2,1]yields a different BST.
Return the number of ways to reorder nums such that the BST formed is identical to the original BST formed from nums.
Since the answer may be very large, return it modulo 109 + 7.
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 <= 10001 <= nums[i] <= nums.length- All integers in
numsare distinct.
Approach overview
Problem Overview: Given an array of distinct integers, insert the values into a Binary Search Tree in the given order. The task is to count how many different reorderings of the array generate the exact same BST structure. The answer can be large, so results are computed modulo 1e9 + 7.
Approach 1: Divide and Conquer with Combinatorial Counting (O(n^2) time, O(n^2) space)
The first element of the array always becomes the BST root. All smaller values must appear in the left subtree and larger values in the right subtree. Split the remaining elements into two arrays using this rule. Recursively compute the number of valid reorderings for the left and right subtrees. The key insight: while elements must preserve their internal order relative to each subtree, they can interleave in many ways. Use combinations C(leftSize + rightSize, leftSize) to count how many ways the two sequences can merge. Multiply this by the recursive counts from both sides. Precompute combinations using Pascal's triangle or factorials with modular inverse. This technique relies heavily on divide and conquer and combinatorics.
Approach 2: Dynamic Programming with Memoized Combinations (O(n^2) time, O(n^2) space)
This approach uses the same BST partitioning idea but focuses on efficient combination calculation. Instead of recomputing binomial coefficients repeatedly, build a DP table where dp[i][j] stores C(i, j). Fill the table using Pascal's identity: C(n, k) = C(n-1, k-1) + C(n-1, k). During recursion, split the array into left and right partitions based on the root value, recursively compute subtree counts, and combine them with dp[leftSize + rightSize][leftSize]. Memoization prevents recomputation of subtree results. The recursion essentially simulates BST construction while DP handles combinatorial counting. This approach integrates dynamic programming with binary search tree properties.
Recommended for interviews: The divide-and-conquer combinatorial approach is what interviewers expect. It demonstrates understanding of BST insertion rules and combinatorial interleavings. Starting with the recursive partitioning idea shows strong reasoning. Adding precomputed combinations or DP optimization proves you can scale the solution to large inputs efficiently.
Approach 1: Divide and Conquer with Combinatorial Counting
This approach uses divide and conquer combined with combinatorial mathematics to determine the number of ways to reorder an array to produce the same BST. The core idea is to recursively split the array into left and right subarrays (as in constructing the BST) and calculate the number of reorderings for each subtree, using combinations to determine how to arrange elements between left and right subarrays.
MOD = 10**9 + 7
def numOfWays(nums):
from math import comb
def count(nums):
if len(nums) <= 2:
return 1
root = nums[0]
left = [x for x in nums if x < root]
right = [x for x in nums if x > root]
left_count = count(left)
right_count = count(right)
return comb(len(left) + len(right), len(left)) * left_count * right_count % MOD
return (count(nums) - 1) % MODimport java.util.*;
public class Solution {
private static final int MOD = (int)1e9 + 7;
public int numOfWays(int[] nums) {
return (int)(helper(nums) - 1);
}
private long helper(int[] nums) {
if (nums.length <= 2) {
return 1;
}
int root = nums[0];
List<Integer> left = new ArrayList<>();
List<Integer> right = new ArrayList<>();
for (int num : nums) {
if (num < root) {
left.add(num);
} else if (num > root) {
right.add(num);
}
}
long leftWays = helper(left.stream().mapToInt(i -> i).toArray());
long rightWays = helper(right.stream().mapToInt(i -> i).toArray());
long comb = comb(left.size() + right.size(), left.size());
return (comb * leftWays % MOD) * rightWays % MOD;
}
private long comb(int n, int k) {
long res = 1;
for (int i = 0; i < k; ++i) {
res = res * (n - i) / (i + 1);
}
return res % MOD;
}
}The solution defines a recursive function count(), which calculates the number of ways to split and merge according to the current root node by checking elements less than (left subtree) and greater than (right subtree) the root. The function comb() is used to calculate the number of ways to interleave left and right subtrees, and this is done recursively. The result is reduced by 1 since the original order is not counted as a valid reordering.
Time Complexity: O(n^2), where n is the size of the array, due to the recursive division and combination calculation.
Space Complexity: O(n^2), because of temporary lists created for left and right subarrays.
Approach 2: Dynamic Programming
Leverage dynamic programming to memoize previously computed results for subarrays. This reduces redundant calculations by storing results of similar subproblems, enabling more efficient computation of the number of reorderings that yield an equivalent BST structure.
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
class Solution {
static const int MOD = 1e9 + 7;
vector<vector<int>> comb;
int dfs(vector<int>& nums) {
if (nums.size() <= 2) return 1;
int root = nums[0];
vector<int> left, right;
for (int num : nums) {
if (num < root) left.push_back(num);
if (num > root) right.push_back(num);
}
long leftWays = dfs(left);
long rightWays = dfs(right);
return ((combs(left.size() + right.size(), left.size()) * leftWays % MOD) * rightWays) % MOD;
}
int combs(int n, int k) {
if (comb[n][k] != -1) return comb[n][k];
if (k == 0 || k == n) return 1;
return comb[n][k] = (combs(n - 1, k - 1) + combs(n - 1, k)) % MOD;
}
public:
int numOfWays(vector<int>& nums) {
int n = nums.size();
comb.resize(n + 1, vector<int>(n + 1, -1));
return dfs(nums) - 1;
}
};using System;
using System.Collections.Generic;
public class Solution {
private const int MOD = 1000000007;
private int[][] dp;
public int NumOfWays(int[] nums) {
int n = nums.Length;
dp = new int[n + 1][];
for (int i = 0; i <= n; i++) {
dp[i] = new int[n + 1];
for (int j = 0; j <= n; j++) {
dp[i][j] = -1;
}
}
return (helper(nums) - 1); // -1 to exclude original ordering
}
private int Comb(int n, int k) {
if (k == 0 || k == n) return 1;
if (dp[n][k] != -1) return dp[n][k];
return dp[n][k] = (Comb(n - 1, k - 1) + Comb(n - 1, k)) % MOD;
}
private int helper(int[] nums) {
if (nums.Length <= 2) return 1;
int root = nums[0];
var left = new List<int>();
var right = new List<int>();
foreach (var num in nums) {
if (num < root) left.Add(num);
else if (num > root) right.Add(num);
}
int leftWays = helper(left.ToArray());
int rightWays = helper(right.ToArray());
return (int)((Comb(left.Count + right.Count, left.Count) * (long)leftWays % MOD) * (long)rightWays % MOD);
}
}The C++ code uses a memoization approach to store combination calculations in a comb table. The dfs function divides the array, computes the number of left and right subarray reorderings, and memoizes the combinations calculated. This avoids redundant calculations and optimizes efficiency.
Time Complexity: O(n^2), due to dynamic programming table filling and repeated subproblem computations.
Space Complexity: O(n^2), necessary for memoization of the combination results and recursive call stack.
Approach 3: Combination Counting + Recursion
We design a function dfs(nums), which is used to calculate the number of solutions of the binary search tree with nums as nodes. Then the answer is dfs(nums)-1, because dfs(nums) calculates the number of solutions of the binary search tree with nums as nodes, while the problem requires the number of solutions of the binary search tree with nums as nodes after reordering, so the answer needs to be subtracted by one.
Next, let's take a look at how dfs(nums) is calculated.
For an array nums, its first element is the root node, so its left subtree elements are smaller than it, and its right subtree elements are larger than it. So we can divide the array into three parts, the first part is the root node, the second part is the elements of the left subtree, denoted as left, and the third part is the elements of the right subtree, denoted as right. Then, the number of elements in the left subtree is m, and the number of elements in the right subtree is n, so the number of solutions for left and right are dfs(left) and dfs(right) respectively. We can choose m positions from m + n positions in array nums to place the elements of the left subtree, and the remaining n positions to place the elements of the right subtree, so that we can ensure that the reordered binary search tree is the same as the original array nums. Therefore, the calculation method of dfs(nums) is:
$
dfs(nums) = C_{m+n}^m times dfs(left) times dfs(right)
where C_{m+n}^m represents the number of schemes to select m positions from m + n positions, which we can get through preprocessing.
Note the modulo operation of the answer, because the value of dfs(nums) may be very large, so we need to take the modulo of each step in the calculation process, and finally take the modulo of the entire result.
The time complexity is O(n^2), and the space complexity is O(n^2). Where n is the length of array nums$.
function numOfWays(nums: number[]): number {
const n = nums.length;
const mod = 1e9 + 7;
const c = new Array(n).fill(0).map(() => new Array(n).fill(0));
c[0][0] = 1;
for (let i = 1; i < n; ++i) {
c[i][0] = 1;
for (let j = 1; j <= i; ++j) {
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
}
}
const dfs = (nums: number[]): number => {
if (nums.length < 2) {
return 1;
}
const left: number[] = [];
const right: number[] = [];
for (let i = 1; i < nums.length; ++i) {
if (nums[i] < nums[0]) {
left.push(nums[i]);
} else {
right.push(nums[i]);
}
}
const m = left.length;
const n = right.length;
const a = dfs(left);
const b = dfs(right);
return Number((BigInt(c[m + n][m]) * BigInt(a) * BigInt(b)) % BigInt(mod));
};
return (dfs(nums) - 1 + mod) % mod;
}func numOfWays(nums []int) int {
n := len(nums)
const mod = 1e9 + 7
c := make([][]int, n)
for i := range c {
c[i] = make([]int, n)
}
c[0][0] = 1
for i := 1; i < n; i++ {
c[i][0] = 1
for j := 1; j <= i; j++ {
c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod
}
}
var dfs func(nums []int) int
dfs = func(nums []int) int {
if len(nums) < 2 {
return 1
}
var left, right []int
for _, x := range nums[1:] {
if x < nums[0] {
left = append(left, x)
} else {
right = append(right, x)
}
}
m, n := len(left), len(right)
a, b := dfs(left), dfs(right)
return c[m+n][m] * a % mod * b % mod
}
return (dfs(nums) - 1 + mod) % mod
}Complexity comparison
| Approach | Complexity |
|---|---|
| Divide and Conquer with Combinatorial Counting | Time Complexity: O(n^2), where n is the size of the array, due to the recursive division and combination calculation. |
| Dynamic Programming | Time Complexity: O(n^2), due to dynamic programming table filling and repeated subproblem computations. |
| Combination Counting + Recursion | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Divide and Conquer with Combinatorial Counting | O(n^2) | O(n^2) | General case; preferred interview solution using recursion and combinatorics |
| Dynamic Programming with Memoized Combinations | O(n^2) | O(n^2) | When repeatedly computing combinations; avoids recalculating binomial coefficients |
Frequently asked questions
Is Number of Ways to Reorder Array to Get Same BST easy or hard?
The problem is classified as Hard because it combines multiple concepts: BST insertion order, divide and conquer recursion, and combinatorics. Efficient solutions also require dynamic programming to compute binomial coefficients under modular arithmetic.
Number of Ways to Reorder Array to Get Same BST Python/Java solution
Python and Java implementations typically use recursion to split the array into left and right subtrees and a precomputed combination table for counting interleavings. The final result is computed modulo 1e9 + 7 to handle large values.
How to solve Number of Ways to Reorder Array to Get Same BST in O(n)?
An exact O(n) solution is not practical because the algorithm must compute combination values and process recursive partitions of the array. The standard optimized solution runs in O(n^2) time using precomputed combinations and divide-and-conquer recursion.
What is the best approach for Number of Ways to Reorder Array to Get Same BST?
The most effective approach uses divide and conquer with combinatorial counting. Treat the first element as the BST root, split the remaining elements into left and right partitions, and recursively compute the number of valid reorderings. The total ways are multiplied by the binomial coefficient that counts how many ways the left and right subsequences can interleave.
Is Number of Ways to Reorder Array to Get Same BST asked at Google/Amazon/Meta?
Variants of BST construction and combinatorial counting problems appear in interviews at companies like Google, Amazon, and Meta. The problem tests understanding of BST properties, recursion, and combinatorics under modular arithmetic.
What data structure is used in Number of Ways to Reorder Array to Get Same BST?
The solution relies on Binary Search Tree properties along with arrays for partitioning elements. A dynamic programming table is used to store binomial coefficients, and recursion simulates BST construction from the insertion order.
What is the time complexity of Number of Ways to Reorder Array to Get Same BST?
The optimal solution runs in O(n^2) time with O(n^2) space. Each recursive step partitions the array and combines results using precomputed binomial coefficients stored in a dynamic programming table.
