任意给定一个正整数N,求一个最小的正整数M(M>1),使得N*M的十进制表示形式里只含有1和0。
第一想法:从小到大枚举M的取值,然后再计算N*M,最后判断它们的乘积是否只含有1和0。该方法时间复杂度太高。
第二想法:因为问题要求N*M的十进制表示形式里只含有1和0,尝试去搜索N*M,需要搜索的空间要小很多。
终极思路:(避免多于的除法验证余数)如1%3=1;10%3=1;101%3=2那么我们可以知道110%3=2。即1和10加上相同的数,他们的余数都相同,所以我们只需要考虑1,11,101,1001而不用考虑10,110,1010。对于modN同余的数,只需要记录最小的一个。形式化论述:假设已经遍历了X的十进制表示有K位时的所有情况,而且也搜索了X=10^K的情况,设10K%N=a。现在要搜索X有K+1位的情况,即X=10K+Y,(0<Y<10K)。把Y按照其对N的余数分类,我们搜索的空间将被分成N-1个子空间。对于每个子空间,其实只需要判断其中最小的元素加上10K是否被N整除即可,而没有必要判断这个子空间里所有元素加上10K是否能被N整除。
伪代码描述:BigInt[i]表示模N等于i的十进制表示形式里只含1和0的最小整数。由于BigInt[i]可能很大,又因为它只有0和1,所以只需要记下1的位置即可。如1001记为(0,3)=10^0+10^3。即BigInt的每个元素是一个变长数组,对于模N等于i的最小X,BigInt的每个元素将存储最小X在十进制中表示1的位置。我们的目标就是求BigInt[0]。
public static String find01Int(int N){
intNoUpdate = 0;
LinkedList<Integer>[]kt = new LinkedList[N];
for(int i = 0;i<N;i++){
kt[i] = new LinkedList<Integer>();
}
kt[1].push(0);
//i=1从十位开始,显然,个位1已经放入kt[1]了
for(int i = 1,j=10%N;;i++,j=(j*10)%N){
boolean flag = false;
if(kt[j].size()==0){
kt[j].clear();
kt[j].push(i);
}
for(int k = 1;k<N;k++){
//用于判断含有余数的项并且是低位(<i),并加上高位后产生的余数是新的
//余数,则把得出该余数的最小值记录下来
if((kt[k].size()>0)&&
(i>kt[k].peek())&&
(kt[(k+j)%N].size()==0)){
flag= true;
//这步赋值很重要,不能漏,因为该最小值前面几项在kt[k]中
for(int p = 0;p<kt[k].size();p++){
kt[(k+j)%N].add(kt[k].get(p));
}
kt[(k+j)%N].push(i);
}
}
if(flag==false)
NoUpdate++;
else
NoUpdate =0;
//如果经过一个循环都没能对BigInt进行更新,就是无解,跳出,或者BigInt[0]已经找到也跳出
if(NoUpdate == N||kt[0].size()>0)
break;
}
if(kt[0].size() == 0){
return null;
}
else{
StringBuilder sb = new StringBuilder();
//输出也很有技巧,例如4的话,kt[0]记录的是2,此时,要输出100,所以要注意//收尾操作
sb.append(1);
for(int i = kt[0].pop();;){
int temp = 0;
boolean t = true;
if(kt[0].size()!=0){
temp= kt[0].pop();
t= false;
}
for(int k = i-1;k>temp;k--){
sb.append(0);
}
if(!t)
sb.append(1);
else
sb.append(0);
i = temp;
if(kt[0].size()==0)break;
}
return sb.toString();
}
}