下一个排列_题解

【题解提供者】史青山

解法

思路

此题属于找规律题,我们可以把一个序列的全排列写出来,然后对比找规律,比如序列 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 
#include 
#include 

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)

拓展

本题可以使用 中库函数 next_permutation完成题中所需功能。

代码展示
#include 
#include 
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;
}

你可能感兴趣的:(算法)