莫泽-德布鲁因数列
问题描述包括打印莫泽-德布鲁因数列的前 N 项,其中 N 由用户输入给出。
莫泽-德布鲁因数列是一个由整数组成的序列,这些整数都是 4 的不同幂之和,即 1、4、16、64 等等。
该序列的前几个数字包括 0、1、4、5、16、17、20、21、64……
该序列始终以零开头,后跟 4 的不同幂之和,例如 $\mathrm{4^{0}}$,即 $\mathrm{4^{1}\:即 4,}$,然后是$\mathrm{4^{0}\:and\:4^{1}\:即\:5}$ 等等。
在这个问题中,我们将给定任意正整数 N,我们的任务是打印最多 N 项的 Moser−de Bruijn 序列。
让我们通过下面给出的例子来理解这个问题。
输入
N=6
输出
0 1 4 5 16 17
解释- 给定的输入是 6,这意味着我们需要打印 Moser−de Bruijn 序列的前 6 项,这是我们所需的输出。
输入
N=12
输出
0 1 4 5 16 17 20 21 64 65 68 69
解释- Moser-de Bruijn 序列的前 12 个项是我们所需的输出。我们可以通过将不同的 4 的幂相加来计算序列的第 N 项。
让我们理解根据输入中的 N 值打印 Moser-de Bruijn 序列前 N 项的算法。
算法
通过观察 Moser-de Bruijn 序列中的数字,我们可以看到序列中的数字遵循它们之间的数学关系。
序列中的前几个数字是 0、1、4、5、16、17、20、21、64、65、68、69、80、81、84……
序列中的数字仅包含不同的 4 的幂和不同的 4 的幂之和。
如果我们从 0 开始考虑序列中数字的位置,我们可以观察到M(0)=0 且 M(1)=1。
并且,每个 N 为偶数的第 N 项都可以表示为:
$$\mathrm{M(N)=4*M(N/2)}$$
类似地,每个 N 为奇数的第 N 项都可以表示为:
$$\mathrm{M(N)=4*M(N/2)+1}$$
让我们尝试利用上述关系找出 Moser−de Bruijn 序列的几个数字。
$$\mathrm{M(0)=0\:\:\:M(1)=1}$$
由于在这种情况下 N 为偶数,因此 M(2) 将等于 4*M(N/2)。因此 M(2)=4*1=4。
由于本例中 N 为偶数,M(3) 等于 4*M(N/2)+1。因此,M(3)=5。
类似地,
$$\mathrm{M(4)=4*M(4/2)=4*4=16}$$
$$\mathrm{M(5)=4*M(5/2)+1=4*4+1=17}$$
我们可以使用此关系计算出数列中直到 N 的所有项。我们将利用上述数列中数字之间的关系,输出 Moser−de Bruijn 数列的前 N 项。
方法 −1(使用递归)
我们将使用递归,利用算法中讨论的公式,找到数列的第 i 项。在我们的方法中,实现递归以打印 Moser-de Bruijn 序列的前 N 项的步骤如下:
为了找到序列的第 i 个数字,我们将创建一个递归函数。
在函数中,如果 i=0,则返回 0;如果 i=1,则返回 1。对于 i 的所有其他值,我们将检查 i 是偶数还是奇数。如果 i 为偶数,则返回 4*rec(i/2);如果 i 为奇数,则返回 4*rec(i/2)+1。
我们可以通过使用递归计算第 i/2 项的值来获得第 i 项的值,从而解决其中的子问题。
使用 for 循环从 i=0 迭代到 i>N,以打印 Moser−de Bruijn 序列的前 N 项。
每次迭代时,我们将调用递归函数来获取序列中第 i 项的值并继续打印它们。
示例
//C++ 代码打印 Moser-de Bruijn 数列的 N 项 #include <bits/stdc++.h> using namespace std; //递归函数获取数列的第 N 项 int rec(int N){ if(N==0){ //as M(0)=0 return 0; } else if(N==1){ //as M(1)=1 return 1; } else if(N&1){ //对于 N 为奇数的情况 return 4*rec(N/2)+1; } else{ //对于 N 为偶数的情况 return 4*rec(N/2); } } //打印序列的前 N 个项 void print_sequence(int N){ //使用 rec() 函数打印至 N 的每个项 for(int i=0;i<N;i++){ cout<<rec(i)<<" "; } } int main() { int N; //输入值 N=22; print_sequence(N); //调用函数 return 0; }
输出
0 1 4 5 16 17 20 21 64 65 68 69 80 81 84 85 256 257 260 261 272 273
方法 2(动态规划)
众所周知,动态规划是针对使用递归解决问题的优化方案。因此,为了优化上述方法,我们将使用动态规划的概念来打印 Moser-de Bruijn 序列的前 N 项。
在这里,我们将第 i 项的值存储在一个 N 数组中,这样我们就不需要计算序列中第 i 个重复子问题的值了。
使用动态规划方法打印 Moser-de Bruijn 序列的前 N 项的步骤如下:
我们将创建一个大小为 N 的向量来存储序列的前 N 项。
为了将数字存储在数组中,我们将创建一个函数,使用算法部分中讨论的公式来查找序列的第 i 个数字。
当向量更新到序列的 N 项时,我们将通过在 for 循环中从 i=0 迭代到 i<N (即 Moser-de Bruijn 序列的前 N 项)来打印向量中存储的数字。
示例
//C++ 代码用于打印 Moser-de Bruijn 数列的前 N 项 #includeusing namespace std; //函数用于使用 dp 更新向量 void calculate_numbers(vector<int>& dp, int N){ dp[0]=0; //as M(0)=0 dp[1]=1; //M(1)=1 for(int i=2;i<N;i++){ if((i&1) == 0){ //如果 i 是偶数 dp[i]=4*dp[i/2]; } else{ //如果 i 是奇数 dp[i]=4*dp[i/2]+1; } } } //打印 Moser-de Bruijn 序列的前 N 项的函数 void print_sequence(vector<int>& dp, int N){ for(int i=0;i<N;i++){ cout<<dp[i]<<" "; } } int main() { int N; //用于输入 N=35; vector<int> dp(N,0); //用于存储序列的前N个元素 //调用函数更新数组至N calculate_numbers(dp,N); //用于打印序列 print_sequence(dp,N); return 0; }
输出
0 1 4 5 16 17 20 21 64 65 68 69 80 81 84 85 256 257 260 261 272 273 276 277 320 321 324 325 336 337 340 341 1024 1025 1028
时间复杂度:O(N) 因为我们在 for 循环中迭代 N 次来更新序列的 N 项。
空间复杂度:O(N) 因为我们使用了一个大小为 N 的向量来存储序列的数字。
结论
本文讨论了莫泽-德布鲁因序列的概念。我们讨论了利用数字之间的数学关系来查找序列中任意第 N 项的算法,并在 C++ 中结合递归和动态规划实现了该算法,以便帮助您更好地理解。
希望本文能帮助您理清关于该主题的所有概念。