字典序和下一个排列

刷Leetcode 的时候遇到字典序求下一个序列的问题,字典序是一种手写起来很简单但是描述起来比较费劲的序列,之前了解的也不多,所以就是...不会做!看题解的时候看给的图片仍然没搞懂什么意思,不过还好在图片的下边附了个动图。看动图一下就看明白了解法,但是还是不明白为什么要这么解。又去网上搜其他的博客,结果除了下一个序列之外,又搜到一篇字典序值(当前序列在字典序中的位置)的解法。那篇博客的思想是没啥问题,只是描述地有些晦涩难懂,重点不突出,所以花了挺长时间才搞懂。这里简单整理一下,让不明白字典序原理的人可以在最短的时间内学会求解字典序值和下一个序列。

1. 字典序

先来看一下[1, 2, 3] 的字典序排列:

字典序值 0 1 2 3 4 5
排列 123 132 213 231 312 321

简单来讲,字典序就是保持左边不变(变得最慢),右边依次从正序到逆序的排列过程。就像我们做两位数的加法,我们从10 加到20 的过程中,中间每加一个值只改变个位数的大小,直到量变达到质变的时候才会去将十位数的数字从1 变成2,大概可以这么理解。

2. 字典序值

字典序值就是当前序列在字典序中的排列位置。那给定一个排列,我们应该如何求出它的字典序值呢?

为了求排列对应的序号,只要该序列到第一个序列即1,2,3…n 所需要移动的次数。

移动原则是a[i] 从大到小移动,对于每一个数字a[i],若i前面比a[i] 小的个数正好是a[i] - 1 个,则这个数不需要向后移动以达到目标序列,否则i 后面必然有a[i] - t - 1 (此处t 的值为a[i] 之前比a[i] 小的数字的个数)个比a[i] 小的数,只要把a[i] 移动到比自己小的数后面才能使得移动中的序列正向目标前进。

因此只要求出每个数的移动次数,然后相加就是该序列的位置。即每个数到正确位置需要移动的次数为(a[i]-t-1) * (a[i]-t-1)!

—— 这一段引自 https://blog.csdn.net/hello_tomorrow_111/article/details/78696294 并略作优化

啥意思,有点模糊,举个栗子来解释一下。我们就以第一点中提到的[1, 2, 3] 的字典序为例,我现在想知道321 在序列中的位置应该怎么计算呢?

(1)找到该序列的第一个序列,即123

(2)从序列左边开始,查找每个值应该移动的位数

  • 首先来看3,与第一个序列相比,3 之前本来应该有两个元素12,但是现在它前面没有比它小的元素,所以它要移动3 - 1 - 0 (a[i] - t - 1),即两位到达正序位置。
  • 接着来看2,2 之前应该是有一个元素1,但是它前面也没有比它小的元素,所以它要向后移动2 - 1 - 0,即一位来达到正序。
  • 对于1 来说,它是排列中最小的元素,它之前不应该有元素,而事实也是如此,所以它不需要移动。

将3 和2 移动的次数相加就是321 在字典序中的字典序值。那么3 和2 分别需要移动多少次才能移动两位或者一位呢,以3 为例来看一下:

321 中的3 往后移动两位需要经历以下流程(回退):231、213、132、123。可以发现,中间经历的次数刚好是(2*2! 的阶乘)(a[i]-t-1) * (a[i]-t-1)!

为什么会得出这个数字,这可能就需要强行理解一番了:我们要把21 移到3 的前面,21 本身的排列有2! 种,但是在移的过程中有可能有两种情况,12 可能全部到了3 的前面,也可能只有一个值在3 的前面。于是就得到移动次数的值2*2! 。

3. 下一个序列

字典序值说完了,说说下一个序列,下一个序列求解的思想非常简单:

(1)从右向左找到第一个左临小于右临的位置,左临的位置标记为i

(2)从右向左找到第一个大于a[i] 的数值,位置为j

(3)交换a[i] 与a[j] 的值

(4)将i 右边的部分排成正序

如果看这个流程看不懂的话,建议去看Leetcode 上的动图,保证看几遍就懂:

https://leetcode-cn.com/problems/next-permutation/solution/

Say sth more (简称SM): 为什么要这么做呢,提供一个栗子结合理解,假设我们要求15499 + 1 的和,这个应该怎么算呢?

解法是:从右向左找到第一个不为9 的数值,将该数值加1,然后该数值以后所有的9 都变成0。此例可以与求解下一个序列的过程结合理解。

4. Show you the code

好了,说了这么多,上代码了,C++的。

/**
 * @author shaoDong
 * @email [email protected]
 * @create date 2018-09-03 07:39:07
 * @modify date 2018-09-03 07:39:07
 * @desc 字典序问题
*/
#include <iostream>
#include <vector>

using namespace std;

void swap(int *a, int *b)
{
    long long t;
    t = *a;
    *a = *b;
    *b = t;
}

int main()
{
    int n, t;
    int pos, temp;
    vector<int> inputs;
    // 存放阶乘值
    vector<int> fac;
    fac.push_back(1);
    // input
	cout<<"请输入元素的个数,以及一个序列"<<endl;
    cin>>n;
    for(int i = 0;i < n; i++)
    {
        cin>>temp;
        inputs.push_back(temp);
        // 初始化阶乘数组
        if(i >= 1) {
            fac.push_back(fac[i - 1] * i);
        }
    }
    // 如果只输入一个元素
    if(n == 1) 
    {
        cout<<0<<endl<<inputs[0]<<endl;  
    }
    // 如果输入两个以上元素
    else if(n >= 2)
    {
        pos = 0;
        for(int i = 0,k = n - 1;i < n-1; i++,k--)
        {
            // t 代表当前元素之前比该元素小的元素的个数
            t = 0;
            for(int j = 0;j < i; j++)
            {
                if(inputs[j] < inputs[i])
                {
                    t++;
                } 
            }
            // inputs[i] - 1 - t 表示该元素需要向后移动的位数
            pos += (inputs[i] - 1 - t) * fac[k];
        }
        cout<<"该序列是第"<<pos<<"个"<<endl;

        int i;
        // 开始计算下一个序列
        for(i = n - 2; i >= 0; i--)
        {
            // 找到第一个左临小于右临的位置
            if(inputs[i] < inputs[i + 1])
            {
                int j = i;
                // 找到右边第一个大于j 元素的位置
                for(int k = n-1; k > j; k--)
                {
                    if(inputs[k] > inputs[j])
                    {
                        swap(&inputs[j], &inputs[k]);
                        j++;
                        for(int h = n - 1; j < h; j++, h--) 
                        {
                            swap(&inputs[j], &inputs[h]);
                        }
                    }
                }
                break;
            }
        }
        // 如果已经是最后一个排列
        if(i < 0) 
        {
            for(int j = 0, k = n - 1; k > j; k--, j++)
            {
                swap(&inputs[j], &inputs[k]);
            }
        }
        cout<<"该序列的下一个序列是:"<<endl;
        for(int i = 0; i < n; i++)
        {
            cout<<inputs[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_33594380/article/details/82377923