题目:给定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的情况,时间复杂度为线性的。