m位二进制数,1个数不超过n,字典序排下第i个数

题目:给定m位字符串数组S,由0和1组成,要求m位中1的个数不超过n,然后找到字典序的第i个数。如:如 m=4 n=2 即1的个数不超过2个  0111就不行,如果找第i=8个 从0000  0001 …… 0110 1000 。第八个就是1000 。写一个函数,输入m,n,i返回那个数。


思路1:

m=4,n=2,i=8,按正常的方法来讲是16中方法,晒去不合格的,即1的数量超过2的,那么还剩下11个,因此取第8个为1000:0000、0001、0010、0011、0100、0101、0110、1000、1001、1010、1100。m=4的情况,则数的大小应该是在0~2^4-1之间,可以判断该数的二进制中1的个数,那这个方法的复杂度是:O(2^n)。

思路2:

采用二叉树的方式,这种方法主要耗费的时间在根据规则创建合法的二叉树上,当到达一个节点时,此时字符串中已经包含的1的个数为k,如果k<n,那么该节点的子节点就有两种选择,0或者1,如果k=n,则只有一个选择,后边的所有元素只能为0,这样最后数叶节点那一层,数第i个就可以了。

思路3:生成排列,首先按照1的思路,对于二进制数的递增序列,其实就是所代表的十进制数的递增序列,而递增都是从最后一位开始增长的。比如:0000->0001->0010。对于这样的序列,要实现递增通常有两种操作:

1)如果末位是0,那么将0变成1,这里1的个数是增加的;2)如果末位是1,那么将1变成0,然后实现进位操作,操作过程中1的个数不变。

对于0011,按照2)操作,变成了0110,实现了递增。因此通过1)和2)两种操作,能实现构造字典序的含指定个数1的01字符。2)的操作会遇到一种特殊情况,比如从0110下一个就是0111,1的个数已经超了,在下一个1000是合法的,那么如何直接从0110构造1000呢?需要对2)操作进行修改,在1的个数等于n的情况下,“末位”指从右往左第一个非零位,因此0110通过2)操作变成了1000 。将算法的伪代码描述如下:


bit[],m,n,i
permutation{
    k,j,num,h,last=m-1
    for(k=1;k<i;){
        num = ones_num(bit,m);
        if(num<n){
                if(bit[last]==0)
                    1)操作
                else
                    2)操作
        }else{
                j=m-1;
                while(j>=0 && bit[j]==0)
                    j--;
                h = j;
                while(h>=0&&bit[h]==1)
                    h--;
                if(h<0)
                    break;
                else if(bit[h]==0){
                    2)操作
                }
            }
    }
}

贴出C++代码:

#include<iostream>
using namespace std;
#define M 10
int bit[M];

int ones_num(int arr[],int len){
    int cnt=0;
    for(int i=0;i<len;i++)
        cnt += arr[i];
    return cnt;
}

void permutation(int m,int n,int i){
    memset(bit,0,sizeof(bit));
    int k,j,num,h;
    for(k=1;k<i;){
        num = ones_num(bit,m);
        if(num<n){
                if(bit[m-1]==0){
                    bit[m-1] = 1;
                    k++;
                }else{
                    bit[m-1] = 0;
                    j = m-2;
                    while(j>=0&&bit[j]==1){
                        bit[j] = 0;
                        j--;
                    }
                    if(j>=0&&bit[j]==0){
                        bit[j] = 1;
                        k++;
                    }
                }
        }else if(num==n){
                j=m-1;
                while(j>=0 && bit[j]==0)
                    j--;
                h = j;
                while(h>=0&&bit[h]==1)
                    h--;
                if(h<0)
                    break;
                else if(bit[h]==0){
                    bit[j] = 0;
                    j--;
                    while(j>h){
                        bit[j] = 0;
                        j--;
                    }
                    bit[h] = 1;
                    k++;
                }
            }
    }
    for(j=0;j<m;j++)
        cout << bit[j]<< " ";
}


int main(){
    permutation(4,2,9);
    return 0;
}


这种生成排列的方法的关键之处就如0110的下一个排列使1000之处,能够省去中间大量的使1的总个数超过1的情况,时间复杂度为线性的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值