1. 题目来源
2. 题目解析
就是求逆序对的个数的问题,求逆序对个数在算法竞赛中还是蛮常见的,可以单独拎出来。常见的有暴力、归并排序、树状数组、线段树等求解方式,在此数据范围为 1e3
,完全可以暴力求解。
思路:
- 可以通过
next_permutation()
函数直接求出对应的排列b
。 - 关键是怎么将问题转化为逆序对问题。
- 顺序枚举
num
的所有字符,找到每个字符在b
序列的字符对应位置,开一个等长的c
数组用于放置字符对应的b
数组的下标位置。那么若将b
序列变成num
序列,其实就是将c
数组中存的下标值,在交换相邻位置前提下变成有序,也就转化成了一般的逆序对问题。 - 值得注意的是,在这个序列中可能存在重复元素问题,我们再映射编号的时候需要注意保证相同元素映射前后的相对位置不变。这个很重要,如,我们将
1a.......1b
映射的时候,有两种情况1a.........1b
,1b..........1a
,前者在交换的过程中就不存在相邻的1a
与1b
的交换,但是1b...1a
若想变成原序列的情况下必然存在一次相邻且交换,所以,最优解情况下一定不会存在这个情况。
这个逆序对问题也可以直接交换来做,官方题解给出了详细的证明,写的不错,值得学习!
从贪心的角度考虑,若 num[i]==b[i]
不需要做操作,否则,直接在 i
的右边找到 num[i]==b[j]
,然后从 i~j
之间相邻字符翻转,模拟相邻交换的操作,将这个 b[j]
调整到 i
的位置,每次调整都让答案 +1
,就这样贪心的来做,就是正确的。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)
映射下标,求解逆序对代码:
class Solution {
public:
int getMinSwaps(string num, int k) {
int n = num.size();
string b = num;
while (k -- ) next_permutation(b.begin(), b.end());
vector<int> c(n);
int cnt[10] = {0};
for (int i = 0; i < n; i ++ ) {
int x = num[i] - '0';
cnt[x] ++ ;
int y = cnt[x];
for (int j = 0; j < n; j ++ ) {
if (num[i] == b[j] && -- y == 0) {
c[i] = j;
break;
}
}
}
int res = 0;
for (int i = 0; i < n; i ++ )
for (int j = i + 1; j < n; j ++ )
if (c[i] > c[j])
res ++ ;
return res;
}
};
官方题解代码,贪心直接按题意模拟:
class Solution {
public:
int getMinSwaps(string num, int k) {
string num_k = num;
for (int i = 0; i < k; ++i) {
next_permutation(num_k.begin(), num_k.end());
}
int n = num.size();
int ans = 0;
for (int i = 0; i < n; ++i) {
if (num[i] != num_k[i]) {
for (int j = i + 1; j < n; ++j) {
if (num[j] == num_k[i]) {
for (int k = j - 1; k >= i; --k) {
++ans;
swap(num[k], num[k + 1]);
}
break;
}
}
}
}
return ans;
}
};