【题解提供者】史青山
解法
思路
此题属于找规律题,我们可以把一个序列的全排列写出来,然后对比找规律,比如序列 1 2 3 4 5,全排列如下:
1 2 3 4 5
1 2 3 5 4
1 2 4 3 5
1 2 4 5 3
1 2 5 3 4
1 2 5 4 3
1 3 2 4 5
1 3 2 5 4
…
我们观察 1 2 5 4 3 -> 1 3 2 4 5 变化过程,发现从右往左遍历过程中,2 5 破坏了递增趋势,然后对右边序列从右往左遍历找到第一个大于 2 的元素 3,然后将 2 和 3 交换位置,右边剩下的序列 5 4 2 按照升序排列得到 2 4 5,最后得到的序列正好是下一个排列。
下面我们探讨一下,假设拿到这个题,根本不知道上面的解法,或者说根本不知道从找规律的角度去解题,那我们能不能尝试着从题目的本质去找寻解题的思路呢?我们分析这题,此题要求的是求下一个排列,一个最直观的想法是,我们可以先把序列的全排列求出来,然后在这个全排列中找到当前的这个序列,然后这个序列位置的下一个序列即为下一个排列,但是,代价是空间复杂度和时间复杂度都高。有没有其他思路呢?
排列的原则是一开始尽量将较大的值往后排,当较大的值在后半序列排完了后,再把它与前面恰好小于它的数交换,然后后边序列全部升序,就能得到它的下一个排序。
也就是以下这三点:
-
在尽可能靠右的低位进行交换,需要从后向前查找。
-
将一个幅度变化小的「大数」 与前面的「小数」交换。
PS:前两步保证了满足交换的俩个数一定是升序。
如 1 6 5 4 3 2 我们就找 1 和 2 让它们进行交换,但是只交换 2 和 1 也不行,因此有了下面这一点。 -
将「大数」换到前面后,需要将「大数」后面的所有数重置为升序,因为升序排列就是字典序最小的排列。
那么后面的数一定是降序,我们需要把他们变成升序,满足了增加幅度但是不那么大满足“下一个排列”。如 1 6 5 4 3 2 的下一个排列是 2 1 3 4 5 6 ,1 2 3 4 6 5 的下一个排列是 1 2 3 5 4 6 。
代码展示
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;
vector<int> a;int main() {int n, x;cin >> n;for (int i = 0; i < n; i++) {cin >> x;a.push_back(x);}int pos = n - 2;//用pos来记录倒数第二个数的下标 从后向前找while (pos >= 0 && a[pos] >= a[pos + 1]) pos--;if (pos >= 0) {int j = n - 1;//找出nums[pos]后面大于nums[pos]的最小数的下标while (a[j] <= a[pos]) j--;swap(a[pos], a[j]);reverse(a.begin() + pos + 1, a.end());//交换完后对nums[pos]后面的数字进行从小到大排列//因为此时nums.begin()+pos+1到nums.end()一定是降序排列,所以只需reverse就是从小到大排列了} else {//说明是最大排列,下一个应该是最小排列reverse(a.begin(), a.end());}for (int i = 0; i < n; i++) cout << a[i] << " ";return 0;
}
算法分析
本程序的时间复杂度为 O ( n ) O(n) O(n)。
拓展
本题可以使用 <algorithm>
中库函数 next_permutation
完成题中所需功能。
代码展示
#include <iostream>
#include <algorithm>
using namespace std;const int N = 109;
int a[N];int main() {int n; cin >> n;for(int i = 0; i < n; i ++) cin >> a[i];next_permutation(a, a + n);for(int i = 0; i < n; i ++) cout << a[i] << ' ';return 0;
}