给定一个正整数 n n n的排列,即 n n n个元素 { 1 , 2 , . . . , n } \{1,2,...,n\} {1,2,...,n}的一个序列,计算出这个排列的字典序值(例如排列 { 1 , 2 , 3 , . . . , n } \{1,2,3,...,n\} {1,2,3,...,n}的字典序值为1,排列 { n , n − 1 , n − 2 , . . . , 1 } \{n,n-1,n-2,...,1\} {n,n−1,n−2,...,1}的字典序值为 n n n。
设置计数器,通过从字典序值为1的序列开始,不断计算字典序为 2 , 3 , . . , n 2,3,..,n 2,3,..,n的序列,每一次检测是否等于目标序列,在得到目标序列时停止计数并输出计数器的值。
时间复杂度 O ( n 3 ∗ n ! ) O(n^3*n!) O(n3∗n!)
代码实现(c++)
#include
using namespace std;
class Solution {
public:
void nextPermutation(vector<int>& nums) { //得到当前排列的下一个排列
int n = nums.size();
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 1; j > i; j--) {
if (nums[j] > nums[i]) {
swap(nums[j], nums[i]);
reverse(nums.begin() + i + 1, nums.end());
return;
}
}
}
reverse(nums.begin(), nums.end());
}
int Get_Order(vector<int>& nums) {
vector<int> a;
int n = nums.size(), cnt = 1;
for (int i = 1; i <= n; i++) a.push_back(i);
while (a != nums) {//判断是否到达目标序列
nextPermutation(a);
cnt++;//计数器递增
}
return cnt;
}
};
int main() {
vector<int> nums = {2,5,4,1,3};
Solution T;
cout << T.Get_Order(nums) << endl;
return 0;
}
// output:47
例子
观察序列 { 2 , 5 , 4 , 1 , 3 } \{2,5,4,1,3\} {2,5,4,1,3},计算比其字典序小的序列有多少个
时间复杂度 O ( n 2 ) O(n^2) O(n2)
代码实现(c++)
#include
using namespace std;
class Solution {
public:
int Get_Order(vector<int>& nums) {
int n = nums.size();
vector<int> fact(n + 1);
//计算1到n的阶乘
fact[0] = 1;
for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
//计算 a_i 后面比其小的数的数量
int ans = 0;
for (int i = 0; i < n; i++) {
int cnt = 0;
for (int j = i + 1; j < n; j++)
if (nums[j] < nums[i]) cnt++;
ans += (cnt * fact[n - i - 1]);
}
return ans + 1; //自身字典序值还需+1
}
};
int main() {
vector<int> nums = {2, 5, 4, 1, 3};
Solution T;
cout << T.Get_Order(nums) << endl;
return 0;
}
//output: 47
我们注意到,在方法2中,主要是计算“ a i a_i ai后面比 a i a_i ai小的数的数量"的过程达到了 O ( n 2 ) O(n^2) O(n2)的时间复杂度,而这一类区间查询问题,能够使用树状数组这一数据结构进行优化,从而使查询的时间复杂度降低到 O ( l o g ( n ) ) O(log(n)) O(log(n))
百科:
树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为Fenwick树,最早由Peter M.Fenwick于1994年以A New Data Structure for Cumulative Frequency Tables为题发表在SOFTWARE PRACTICE AND
EXPERIENCE。其初衷是解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和,区间和。[摘自百度百科]
A Fenwick tree or binary indexed tree is a data structure that can efficiently update elements and calculate prefix sums in a table of numbers. [摘自维基百科]
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
代码实现(c++)
#include
using namespace std;
class Solution {
private:
int n;
public:
int lowbit(int x) { return x & (-x); }
void update(vector<int>& c, int i, int k) {
while (i < c.size()) {
c[i] += k;
i += lowbit(i);
}
}
int getsum(vector<int>& c, int i) {
int ans = 0;
while (i > 0) {
ans += c[i];
i -= lowbit(i);
}
return ans;
}
vector<int> countSmaller(vector<int>& A) {//树状数组实现查询比当前数小且在该数后面的数的数量,即cnt_i
vector<int> c(n, 0);
vector<int> res(A.size(), 0);
for (int i = A.size() - 1; i >= 0; i--) {
res[i] += getsum(c, A[i]);
update(c, A[i], 1);
}
return res;
}
int Get_Order(vector<int>& nums) {
n = nums.size();
vector<int> fact(n + 1);
//计算1到n的阶乘
fact[0] = 1;
for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
//计算 a_i 后面比其小的数的数量
vector<int> cnt(n);
cnt = countSmaller(nums);
int ans = 0;
for (int i = 0; i < n; i++) ans += (cnt[i] * fact[n - i - 1]);
return ans + 1; //自身字典序值还需+1
}
};
int main() {
vector<int> nums = {2, 5, 4, 1, 3};
Solution T;
cout << T.Get_Order(nums) << endl;
return 0;
}
在求解“ a i a_i ai后面比 a i a_i ai小的数的数量"的问题上,还可以使用如下方法:
它们的时间复杂度和方法三一样,此处不再列举。