[LeetCode]problem 60. Permutation Sequence

来源:互联网 发布:linux内核开发赚钱吗 编辑:程序博客网 时间:2024/06/04 01:28

TAG

类·进制转换

数学枚举找规律

题目链接

方法

首先,第一眼看过去,没看出来规律。

看第二眼,找到了规律: 第高位往低位看,整个随机序列的最高位从1到n的,看某一确定高位下的序列,会发现次高位也是由低到高,只不过不断跳过之前位上已经出现过的单词。从递归的角度来说,这个n位排序随机序列的产生过程如下:

NUM_SEQ = {}NUM_LEN = kdef GENERATE(num_prefix):    for i = 1 -> n :        if i in num_prefix : continue        new_num_prefix = num_prefix + [ i ]        if len(new_num_prefix) == NUM_LEN :             NUM_SEQ.append(new_num_prefix)            continue        else :            GENERATE(new_num_prefix)

(万万没想到写伪代码竟然写成了Python…)

OK,上面顺序生成序列的过程就算是弄明白了。但是题目是找第k的随机数,可不是要求全部的数。当然不能用枚举的方法来做了,绝对会TLE的吧(9!的也太大了)。

所以我们的问题就变成了,已经知道了上面的生成规则,可不可以直接有第k个数来反推出各位的值呢?

画了一个4!的式子: 4! = 4 x 3 x 2 x 1, 突然有了想法——将k不断除以和取模1,2,3,4,得到的值是否就是各位上的值(这里的值是相对1的偏移值,还得除去前面已经出现过的数)呢?

随机算了个简单的,恰恰如此!

(列一下吧…)

比如第7个数,首先要把7减去1(作为从0开始计数——为什么会这样,因为第一个数各位必然是0啊,如果不减肯定就不对了)。

那么就是说val = 6

  1. 6 / 1 = 6 , 6 % 1 = 0 -> 第1位是0 (偏移0)

  2. 6 / 2 = 3 , 6 % 2 = 0 -> 第2为是0

  3. 3 / 3 = 1 , 3 % 3 = 0 -> 第3为是0

  4. 1 / 4 = 0 , 1 % 4 = 1 -> 第4位是1

ok,最后val的值为0了,就停止。

然后倒过去看(因为是先生成高位),4位数

  1. 第4位是1,表示相对于1偏移1,那么就是2

  2. 第3位是0,表示相对于1的偏移是0,此时1也没有出现,故就是1

  3. 第2位是0,表示相对于1的偏移是0,但此时1、2都出现了,故只能选3

  4. 第1位是0,表示相对于1的偏移是0,这是只剩下4

故4位数的随机序列中,第7个就是 2134 .

其实最低位(上述‘第1位’)偏移肯定都是0(从直觉上理解,因为最后一位只有一种选择,从上面的算式理解,正整数模1都是0),所以可以跳过计算,当然不跳过也没什么影响。

上面在想的过程中,其实越来越清晰了——

这不就是类似于十进制转二进制的思想吗!!

然后我们再类别一下,这的确可以认为是一个进制转换问题。n位排列数,其每位的权从高往低,就是(n-1)!,(n-2)!,…,1 (更直观的说,每位的可选数值个数分别是n,n-1,n-2,…,1).类比一下2进制,每位权值就是 2^(n-1),2^(n-2), …, 2^0 (每位的可选数值只有两个). 所以,相对于二进制,这是一个动态进制的数!此外,比二进制转换多一步就是,后面的数与前面是不重复的,所以还有一个选择数字的过程。

转换为进制转换的问题,上述的计算方式就更加可以理解了。这个问题也变得更加有清晰。

代码

class Solution {public:    string getPermutation(int n, int k) {        assert(k > 0);        string result(n,'1');         vector<int> offsets(n,0);        vector<bool> hasAppeared(n,false);        calcOffset(n, k, offsets);        // generate k-th number        for(int bitPos = n-1 ; bitPos >= 0 ; --bitPos)        {            for(int candidateVal = 1 ; candidateVal <= n; ++candidateVal)            {                if(hasAppeared[candidateVal-1]){ continue ;}                if( 0 == offsets[bitPos] )                 {                    result[n-1-bitPos] = candidateVal - 1 + '1' ;                    hasAppeared[candidateVal-1] = true;                    break;                }                else{ --offsets[bitPos]; }            }        }        return result;    }private:    void calcOffset(int bitNum, int order, vector<int> &offsets)    {        --order ; // let it count from 0 instead of 1        size_t bitPos = 0 ;        while(bitPos < bitNum && order != 0) // in fact , if order is valid , there is no need for condition `bitPos < bitNum`        {            int curPosWeight = bitPos + 1;            offsets[bitPos] = order % curPosWeight ;            order /= curPosWeight;            ++bitPos;        }    }};
0 0