试题答案_找符合条件的整数

 分析与解法

题目中的直接做法显然不是一个令人满意的方法。还有没有其他的方法呢?答案是肯定的。

可以做一个问题的转化。由于问题中要求N * M的十进制表示形式里只含有10,所以N * MM相比有明显的特征。我们不妨尝试去搜索它们的乘积N * M,这样在某些情况下需要搜索的空间要小很多。另外,搜索N * M,而不去搜索M,其实有一个更加重要的原因,就是当M很大时,特别是当M大于232时,某些机器就可能没法表示M了,我们就得自己实现高精度大整数类。但是考虑N * M的特点,可以只存储N * M的十进制表示中“1”的位置,这样就可以大大缩小表示N * M所需要的空间,从而使程序能处理数值很大的情况。因此,考虑到程序的推广性,选择了以N * M为目标进行计算。

换句话说,就是把问题从“求一个最小的正整数M,使得N * M的十进制表示形式里只含有1 0 变成求一个最小的正整数X,使得X的十进制表示形式里只含有10,并且XN整除。

我们先来看一下X的取值,X从小到大有如下的取值:110111001011101111000100110101011110011011110111110000……

如果直接对X进行循环,就是先检查X=1是否可以整除N,再检查X=10,然后检查X=11,接着检查X=100(就像遍历二进制整数一样遍历X的各个取值)。

但是这样处理还是比较慢,如果X的最终结果有K位,则要循环搜索2K次。由于我们的目标是寻找最小的X,使得X mod N= 0,我们只要记录mod N = i0 <= i < N)的最小X就可以了。这样通过避免一些不必要的循环,可以达到加速算法的目的。那么如何避免不必要的循环呢?先来看一个例子。

N=3X=1,再引入一个变量JJ = X % N。直接遍历X,计算中间结果如表2-1所示。

2-1

Num

1

2

3

4

5

6

7

N

3

3

3

3

3

3

3

X

1

10

11

100

101

110

111

J

1

1

2

1

2

2

0

2-1计算110 % 3是多余的。原因是1103的余数相同,所以1011103的余数相同,那么只需要判断101是否能整除3就可以了,而不用判断110是否能整除3。并且,如果X的最低3位是110,那么可以通过将101替换110得到一个符合条件的更小正整数。因此,对于mod N同余的数,只需要记录最小的一个。

有些读者可能会问,当X循环到110时,我怎么知道1103的余数相同呢?其实,X=1X=10是否能整除3,在X循环到110时都已经计算过了,只要在计算X=1X=10时,保留X % N的结果,就可以在X=110时作出判断,从而避免计算X % N

以上的例子阐明了在计算中保留X除以N的余数信息可以避免不必要的计算。下面给出更加形式化的论述。

假设已经遍历了X的十进制表示有K位时的所有情况,而且也搜索了X=10K的情况,设10K%N = a。现在要搜索XK+1位的情况,即X=10K+Y,(0<Y<10K)。如果用最简单的方法,搜索空间(Y的取值)将有2K-1个数据。但是如果对这个空间进行分解,即把Y按照其对N的余数分类,我们的搜索空间将被分成N-1个子空间。对于每个子空间,其实只须要判断其中最小的元素加上10K是否能被N整除即可,而没有必要判断这个子空间里所有元素加上10K是否能被N整除。这样搜索的空间就从2K-1维压缩到了N-1维。但是这种压缩有一个前提,就是在前面的计算中已经保留了余数信息,并且把Y的搜索空间进行了分解。所谓分解,从技术上讲,就是对于“XN”的各种可能结果,保留一个对应的已经出现了的最小的X(即建立一个长度为N的“余数信息数组”,这个数组的第i位保留已经出现的最小的模NiX)。

那么现在的问题就是如何维护这个“余数信息数组”了。假设已经有了X的十进制表示有K位时的所有余数信息。也有了X=10K的余数信息。现在我们要搜索XK+1位的情况,也即X=10K+Y,(0<Y<10K)时,X除以N的余数情况。由于已经有了对Y的按除N的余数进行的空间分解情况,即Y<10K的余数信息数组。我们只须要将10K%N的结果与余数信息数组里非空的元素相加,再去模N,看看会不会出现新的余数即可。如果出现,就在余数信息数组的相应位置增添对应的X。这一步只须要N次循环。

综上所述,假设最终的结果XK位,那么直接遍历X,须要循环2K次,而按照我们保留余数信息避免不必要的循环的方法,最多只需要(K-1*N步。可以看出,当最终结果比较大时,保留余数信息的算法具有明显的优势。

下面是这个算法的伪代码:BigInt[i]表示模N等于i的十进制表示形式里只含10的最小整数。由于BigInt[i]可能很大,又因为它只有01,所以,只须要记下1的位置即可。比如,整数1001,记为(0, 3= 100+103。即BigInt的每个元素是一个变长数组,对于模N等于i的最小XBigInt的每个元素将存储最小X在十进制中表示“1”的位置。我们的目标就是求BigInt[0],如代码清单2-17所示。

代码清单2-17

// 初始化

     for(i = 0; i < N; i++)

          BigInt[i].clear();

     BigInt[1].push_back(0);

 

     int NoUpdate = 0;

     for(i=1,j=10%N; ; i++,j=(j*10)%N)

     {

          bool flag = false;

          if(BigInt[j].size() == 0)

          {

               flag = true;

               // BigInt[j] = 10^i, (10^i % N = j)

               BigInt[j].clear();

               BigInt[j].push_back(i);

          }

          for(k = 1; k < N; k++)

          {

               if((BigInt[k].size() > 0)

                    && (i > BigInt[k][BigInt[k].size() - 1])

                    && (BigInt[(k + j) % N].size() == 0))

               {

                    // BigInt[(k + j) % N] = 10^i + BigInt[k]

                    flag = true;

                    BigInt[(k + j) % N] = BigInt[k];

                    BigInt[(k + j) % N].push_back(i);

               }

          }

          if(flag == false) NoUpdate++;

          else NoUpdate=0;

          // 如果经过一个循环节都没能对BigInt进行更新,就是无解,跳出。

          // 或者BigInt[0] != NULL,已经找到解,也跳出。

          if(NoUpdate == N || BigInt[0].size() > 0)

               break;

     }

     if(BigInt[0].size() == 0)

     {

          // M not exist

     }

     else

     {

          // Find N * M = BigInt[0]

     }

在上面的实现中,循环节取值N(其实循环节小于等于N,循环节就是最小的c,使得 10c mod N = 1)。

这个算法其实部分借鉴了动态规划算法的思想。在动态规划算法的经典实例“最短路径问题”中,当处理中间结点时,只须要得到从起点到中间结点的最短路径,而不需要中间结点到那个端点的所有路径信息。“只保留模N的结果相同的X中最小的一个”的方法,与此思路相似。

扩展问题

1.    于任意的N,一定存在M,使得N * M的乘积的十进制表示只有01吗?

2.    怎样找出满足题目要求的NM,使得N * M < 216,且N+M最大?

 

相关资源:

《编程之美》编辑部 | 《编程之美》豆瓣 | 《编程之美》互动网购买 | 作者Blog

作者豆瓣

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值