Moser-de Bruijn 序列
题目要求打印 Moser-de Bruijn 序列的前 N 项,其中 N 将作为用户输入。
Moser-de Bruijn 序列是一个由整数构成的序列,这些整数不过是 4 的不同次幂的和,例如 1、4、16、64 等等。
该序列的前几项包括 0、1、4、5、16、17、20、21、64……
该序列总是以零开头,然后是 4 的不同次幂的和,例如 4⁰,即 1;4¹,即 4;然后是 4⁰ 和 4¹ 的和,即 5,依此类推。
在这个问题中,我们将得到任意正整数 N,我们的任务是打印 Moser-de Bruijn 序列直到 N 项。
让我们通过以下示例了解这个问题。
输入
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}$$
M(2) 将等于 4*M(N/2),因为在这种情况下 N 是偶数。所以 M(2)=4*1=4。
M(3) 将等于 4*M(N/2)+1,因为在这种情况下 N 是奇数。所以 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++ code to print the N terms of Moser-de Bruijn Sequence #include <bits/stdc++.h> using namespace std; //recursive function to get the Nth term of the sequence 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){ //for the case when N is odd return 4*rec(N/2)+1; } else{ //for the case when N is even return 4*rec(N/2); } } //to print the first N terms of the sequence void print_sequence(int N){ //for printing each term upto N using the rec() function for(int i=0;i<N;i++){ cout<<rec(i)<<" "; } } int main() { int N; //for input value N=22; print_sequence(N); //calling the function 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
示例
//C++ code to print the first N terms of Moser-de Bruijn Sequence #include <bits/stdc++.h> using namespace std; //function to update the vector using 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){ //if i is even dp[i]=4*dp[i/2]; } else{ //if i is odd dp[i]=4*dp[i/2]+1; } } } //function to print the first N terms of the Moser-de Bruijn sequence void print_sequence(vector<int>& dp, int N){ for(int i=0;i<N;i++){ cout<<dp[i]<<" "; } } int main() { int N; //for taking input N=35; vector<int> dp(N,0); //to store first N terms of the sequence //calling the function to update the array up to N calculate_numbers(dp,N); //to print the sequence 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 的向量来存储序列的数字。
结论
本文讨论了 Moser-de Bruijn 序列的概念。我们讨论了使用数字之间数学关系查找序列中任何第 N 项的算法,我们在 C++ 中使用递归和动态规划这两种方法来实现我们的方法,以便更好地让您理解。
我希望您觉得这篇文章有助于澄清您关于该主题的所有概念。