Hard
题目描述
给定一个数组 nums 表示从 1 到 n 的整数的一个排列。我们将按顺序将 nums 的元素插入到一个初始为空的二叉搜索树(BST)中来构造 BST。找出重新排列 nums 的不同方法数,使得构造的 BST 与从原始数组 nums 形成的 BST 相同。
例如,给定 nums = [2,1,3],我们将有 2 作为根,1 作为左子节点,3 作为右子节点。数组 [2,3,1] 也会产生相同的 BST,但 [3,2,1] 会产生不同的 BST。
返回重新排列 nums 的方法数,使得形成的 BST 与从 nums 形成的原始 BST 相同。
由于答案可能很大,请返回答案模 10^9 + 7。
示例 1:
输入: nums = [2,1,3]
输出: 1
解释: 我们可以将 nums 重新排列为 [2,3,1],这将产生相同的 BST。没有其他方法重新排列 nums 来产生相同的 BST。
示例 2:
输入: nums = [3,4,5,1,2]
输出: 5
解释: 以下 5 个数组将产生相同的 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]
示例 3:
输入: nums = [1,2,3]
输出: 0
解释: 没有其他 nums 的排序会产生相同的 BST。
约束条件:
1 <= nums.length <= 10001 <= nums[i] <= nums.lengthnums中的所有整数都是不同的
解题思路
这是一道经典的分治 + 组合数学问题。
核心思路:
- BST的构建规律:第一个元素总是根节点,小于根的元素会进入左子树,大于根的元素会进入右子树
- 要保持BST结构不变,根节点必须保持在第一位,但左右子树内的元素可以重新排列
分治策略:
- 以根节点为分界,将数组分为左子树序列和右子树序列
- 递归计算左右子树各自的排列方案数
- 使用组合数学计算合并方案数:在总共 left_size + right_size 个位置中选择 left_size 个位置放左子树元素
组合数计算: 如果左子树有 m 个元素,右子树有 n 个元素,那么合并方案数为 C(m+n, m),表示从 m+n 个位置中选择 m 个位置放左子树元素。
实现细节:
- 预计算组合数避免重复计算
- 使用递归处理子树
- 结果需要模 10^9 + 7
时间复杂度主要在于递归分治和组合数计算,整体为 O(n²)。
代码实现
class Solution {
public:
int numOfWays(vector<int>& nums) {
int n = nums.size();
if (n <= 2) return 0;
const int MOD = 1e9 + 7;
// 预计算组合数
vector<vector<long long>> C(n + 1, vector<long long>(n + 1, 0));
for (int i = 0; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
}
function<long long(vector<int>&)> dfs = [&](vector<int>& arr) -> long long {
if (arr.size() <= 2) return 1;
int root = arr[0];
vector<int> left, right;
for (int i = 1; i < arr.size(); i++) {
if (arr[i] < root) {
left.push_back(arr[i]);
} else {
right.push_back(arr[i]);
}
}
long long leftWays = dfs(left);
long long rightWays = dfs(right);
return (((leftWays * rightWays) % MOD) * C[left.size() + right.size()][left.size()]) % MOD;
};
return (dfs(nums) - 1 + MOD) % MOD;
}
};
class Solution:
def numOfWays(self, nums: List[int]) -> int:
n = len(nums)
if n <= 2:
return 0
MOD = 10**9 + 7
# 预计算组合数
C = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(n + 1):
C[i][0] = 1
for j in range(1, i + 1):
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD
def dfs(arr):
if len(arr) <= 2:
return 1
root = arr[0]
left = [x for x in arr[1:] if x < root]
right = [x for x in arr[1:] if x > root]
left_ways = dfs(left)
right_ways = dfs(right)
return (left_ways * right_ways * C[len(left) + len(right)][len(left)]) % MOD
return (dfs(nums) - 1) % MOD
public class Solution {
public int NumOfWays(int[] nums) {
int n = nums.Length;
if (n <= 2) return 0;
const int MOD = 1000000007;
// 预计算组合数
long[,] C = new long[n + 1, n + 1];
for (int i = 0; i <= n; i++) {
C[i, 0] = 1;
for (int j = 1; j <= i; j++) {
C[i, j] = (C[i-1, j-1] + C[i-1, j]) % MOD;
}
}
long Dfs(List<int> arr) {
if (arr.Count <= 2) return 1;
int root = arr[0];
List<int> left = new List<int>();
List<int> right = new List<int>();
for (int i = 1; i < arr.Count; i++) {
if (arr[i] < root) {
left.Add(arr[i]);
} else {
right.Add(arr[i]);
}
}
long leftWays = Dfs(left);
long rightWays = Dfs(right);
return (leftWays * rightWays % MOD * C[left.Count + right.Count, left.Count]) % MOD;
}
return (int)((Dfs(new List<int>(nums)) - 1 + MOD) % MOD);
}
}
var numOfWays = function(nums) {
const n = nums.length;
if (n <= 2) return 0;
const MOD = 1e9 + 7;
// 预计算组合数
const C = Array(n + 1).fill().map(() => Array(n + 1).fill(0));
for (let i = 0; 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;
}
}
function dfs(arr) {
if (arr.length <= 2) return 1;
const root = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < root) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
const leftWays = dfs(left);
const rightWays = dfs(right);
return (leftWays * rightWays % MOD * C[left.length + right.length][left.length]) % MOD;
}
return (dfs(nums) - 1 + MOD) % MOD;
};
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(n²) |
| 空间复杂度 | O(n²) |
说明:
- 时间复杂度:O(n²) - 主要由预计算组合数 O(n²) 和递归分治 O(n) 组成
- 空间复杂度:O(n²) - 组合数数组占用 O(n²) 空间,递归调用栈深度 O(n)