题目:
任意给定一个正整数N,求一个最小的正整数M(M>1),使得N * M的十进制表示形式里只含有1和0。
看了题目,我很自然的想到了枚举。然后看N * M的十进制是否只包含1和0。当然,这种暴力的解法不是我们想要的。
换一种枚举方法,我们枚举N*M的取值效果怎么样呢?因为N * M的只包含1和0,所以对于K位的N*M,需要搜索2的K次方。
因为找的是最小整数,所以可以采用BFS(宽度优先搜索)。如果最终解使得N * M有k位,那么总共要搜索2的(k+1)次方。有什么方法可以剪枝么?
答案是肯定的。
当N = 3时,看如下搜索树,括号内容表示mod N的值:
令X = 101 Y=110,可知X与Y同余。
所以10X + 0与10Y + 0同余,10X + 1与10Y + 1同余,并且易知,从X继续往下搜索的值,一定比Y的小。要求最小的M,我们还有必要拓展Y节点么?
综上所述,采用BFS方法,在每一层中,如果出现余数相同的节点,只需要拓展最先出现的即可。
《编程之美》介绍的是另一种更妙的方法。
枚举M也好,BFS搜索N * M也好,我们有没有想过N * M超出整数范围?难道对于大整数,我们还要手写一个大数类?好麻烦…………
首先要解决的是,对于大整数,我们如果表示呢?
因为结果N * M只包含1和0,我们可以用一个vector保留1出现的位数。比如11001,可以用 {0 3 4} 表示。这种方法很省空间。
回想一下刚才提到的,BFS的剪枝方法——同余的数只保留最小的!
看个实例大家就好懂了:
如果知道100 mod 3 =1
10 mod 3 = 1
1 mod 3 = 1
110 mod 3 = 2
如何找到比110更小而且mod 3 = 2的数呢?用1去替换110中的10就OK了。因为1和10同余,所以保留余数最小的即可,毕竟我们找的是满足mod N = 0最小且只含有0和1的数。
我的代码如下:
#include <iostream>
#include <vector>
using namespace std;
void display(vector<int> &v)
{
int t = 0;
for (int i = v.size() - 1;i >= 0;i--)
{
t = v[i];
cout << '1';
while(i > 0 && --t != v[i - 1])
cout << '0';
}
while(t--)
cout << '0';
cout << endl;
}
void searchTheNum(vector<vector<int>> &v, int n)
{
for (int i = 0;i < n;i++)
{
vector<int> nv;
nv.clear();
v.push_back(nv);
}
v[1].push_back(0);
int j;
for (int i = 1, j = 10 % n; ;i++, j = (j * 10) % n)
{
if (0 == v[j].size())
{
v[j].push_back(i);
}
for (int k = 0;k < v.size();k++)
{
if (v[k].size() > 0
&& v[(k + j) % n].size() == 0
&& i > v[k][v[k].size() - 1])
{
v[(k + j) % n] = v[k];
v[(k + j) % n].push_back(i);
}
}
if (v[0].size())
{
display(v[0]);
return;
}
}
}
int main()
{
vector<vector<int>> v;
int n = 99;
searchTheNum(v, n);
return 0;
}
对于书中的拓展问题1:
对于任意的N,一定存在M,使得N*M的乘积的十进制表示只有0和1吗?
我百度到的答案是肯定存在,所以我的代码就假设一定存在解。
实际上书中的代码用了抽屉原理判断是否有解。
拓展问题2:
怎么找出满足题目要求的N 和 M,使得N * M < 2的16次方,且N + M最大?
根据N定出M的上界,并在这个范围内进行查找。
用如上的思路都可以解决。
BFS:先拓展值较大的点,每层同余的数只保留值大的。
书中的方法:也是要保留大值。
这些都还要加上边界的判断。
参考资料:
《编程之美》 2.8 找符合条件的整数
http://www.cnblogs.com/bvbook/archive/2009/02/06/1385448.html