目录
一、题目
1、题目描述
给你一个整数
n
和一个二维数组requirements
,其中requirements[i] = [endi, cnti]
表示这个要求中的末尾下标和 逆序对 的数目。整数数组
nums
中一个下标对(i, j)
如果满足以下条件,那么它们被称为一个 逆序对 :
i < j
且nums[i] > nums[j]
请你返回
[0, 1, 2, ..., n - 1]
的排列
perm
的数目,满足对 所有 的requirements[i]
都有perm[0..endi]
恰好有cnti
个逆序对。由于答案可能会很大,将它对
109 + 7
取余 后返回。
2、接口描述
python3
python
class Solution:
def numberOfPermutations(self, n: int, requirements: list[list[int]]) -> int:
cpp
cpp
class Solution {
public:
int numberOfPermutations(int n, vector<vector<int>>& requirements) {
}
};
C#
cs
public class Solution {
public int NumberOfPermutations(int n, int[][] requirements) {
}
}
3、原题链接
二、解题报告
1、思路分析
每个前缀都要满足限制,我们不妨考虑每个前缀最后一个位置填什么?
因为是一个排列,所以我们不关注具体的数字,只关注所填数字的rank
也就是说 我们在最右端填一个数字x,就能得到左边有几个数字和x构成逆序对
这样就可以找到子问题
定义状态 f(i, j) 为 考虑到 下标 i 为止,我们构成 j 个逆序对的方案数
ed = 当前位置的逆序对数目限制,如果没有要求就是M
那么 如果 cnt[i - 1] >= 0,那么我们当前位置应该填一个不影响 cnt[i - 1] 的数字:
f[i][j] = f[i][j - cnt[i - 1]], 如果 cnt[i - 1] <= j <= min(i + cnt[i - 1], ed)
如果 cnt[i - 1] = -1,那么当前位置填的数字和左边的逆序对贡献只要不超过 ed 即可
f[i][j] = sum(f[i - 1][j - k] for k in range(min(i, j) + 1)) % P
这个逻辑显然可以前缀和优化
在具体代码实现的时候我们可以滚动数组优化掉第一维
2、复杂度
时间复杂度: O(NM),M为 cnt 的值域,空间复杂度:O(NM)
3、代码详解
python3
python
P = 1_000_000_007
class Solution:
def numberOfPermutations(self, n: int, requirements: list[list[int]]) -> int:
st = [-1] * n
st[0] = M = 0
for end, cnt in requirements:
st[end] = cnt
M = max(M, cnt)
if st[0]: return 0
f = [0] * (M + 1)
f[0] = 1
for i in range(1, n):
s = st[i - 1]
ed = M if st[i] < 0 else st[i]
if s >= 0:
for j in range(M + 1):
f[j] = f[s] if s <= j <= min(i + s, ed) else 0
else:
for j in range(1, ed + 1):
f[j] += f[j - 1]
if f[j] >= P: f[j] -= P
for j in range(ed, i, -1):
f[j] = (f[j] - f[j - i - 1]) % P
return f[st[n - 1]]
cpp
cpp
constexpr int P = 1'000'000'007;
class Solution {
public:
int numberOfPermutations(int n, vector<vector<int>>& requirements) {
int M = 0;
std::vector<int> st(n, -1);
st[0] = 0;
for (auto &r : requirements) {
int end = r[0], cnt = r[1];
st[end] = cnt;
M = std::max(M, cnt);
}
if (st[0]) {
return 0;
}
std::vector<int> f(M + 1);
f[0] = 1;
for (int i = 1; i < n; ++ i) {
int s = st[i - 1];
int ed = st[i] < 0 ? M : st[i];
if (s >= 0) {
for (int j = 0; j <= M; ++ j) {
f[j] = (s <= j && j <= std::min(ed, i + s)) ? f[s] : 0;
}
} else {
for (int j = 1; j <= ed; ++ j) {
f[j] += f[j - 1];
if (f[j] >= P) {
f[j] -= P;
}
}
for (int j = ed; j > i; -- j) {
f[j] = ((f[j] - f[j - i - 1]) % P + P) % P;
}
}
}
return f[st[n - 1] < 0 ? M : st[n - 1]];
}
};
C#
cs
public class Solution {
public readonly int P = 1_000_000_007;
public int NumberOfPermutations(int n, int[][] requirements) {
int M = 0;
int[] st = new int[n];
Array.Fill(st, -1);
st[0] = 0;
foreach(var v in requirements) {
st[v[0]] = v[1];
M = Math.Max(M, v[1]);
}
if (st[0] > 0) {
return 0;
}
int[] f = new int[M + 1];
f[0] = 1;
for (int i = 1; i < n; ++ i) {
int s = st[i - 1];
int ed = st[i] < 0 ? M : st[i];
if (s >= 0) {
for (int j = 0; j <= M; ++ j) {
f[j] = s <= j && j <= Math.Min(i + s, ed) ? f[s] : 0;
}
} else {
for (int j = 1; j <= ed; ++ j) {
f[j] += f[j - 1];
if (f[j] >= P) {
f[j] -= P;
}
}
for (int j = ed; j > i; -- j) {
f[j] = ((f[j] - f[j - i - 1]) % P + P) % P;
}
}
}
return f[st[n - 1] < 0 ? M : st[n - 1]];
}
}