如何解决找到斐波那契单词序列的第 n 个单词 (Java)
我正在尝试解决这个任务:
设 x[0] =0; x[1] =1; x[i] = x[i-2] + x[i-1]
找到单词 x[n] 的第 k 个字符,看它是 '0' 还是 '1',边界为 1
例如,对于序列 0110110101101,我们有
x[0] = 0
x[1] = 1
x[2] = 01
x[3] = 101
x[4] = 01101
x[5] = 10101101
当我使用 n = 44 及更高版本进行测试时,IDE 会抛出 OutOfMemoryError Java 堆空间。我知道我正在做的方式将存储序列的第 n 个单词、第 n-1 个单词和第 n-2 个单词,这会占用大量内存,但我想不出更好的方法。
在对论文进行一些草稿工作后,我还看到要找到 n = 3 之后的第 n 个单词,while 循环只需要运行 n-2 次但没有运气实现
我还尝试将每个单词存储在 String ArrayList 中并使用递归进行,但效率更低
感谢任何提示
这是我的代码
import java.util.Scanner;
public class BinarySequence {
public static void main(String[] args) {
Scanner read = new Scanner(system.in);
int t = read.nextInt(); //number of test to run
while (t>0){
String s0 = "0";
String s1 = "1";
int n = read.nextInt(); //nth fibonacci word
int k = read.nextInt(); // kth char of the word
System.out.println(fib(s0,s1,n-1).charat(k-1));
t--;
}
}
private static String fib(String s0,String s1,int n) {
String ans ="";
if(n==0)
return s0;
else if(n==1)
return s1;
else {
while(n>=0){
ans = s0+s1;
s0=s1;
s1=ans;
n--;
}
return ans;
}
}
}
解决方法
输入 k
被限制在 1
和 92
之间,因此计算序列字符串只需要前 92 个字符。但是,对于每个不同的 x[i]
值,字符串的开头都会发生变化。对于前十一个¹ x[i]
值,字符串取决于x[i-1]
和x[i-2]
的完整值,但在第十一个x[i]
值之后/处{{1} 的第一个字符串} 已经足够长,x[i-2]
的值不再重要,因为它在结果的末尾连接。较大索引的 x[i-1]
和 x[i-1]
值可以如下所示:
x[i-2]
假设 x[i-1] = 1111111...1111111 + xxxxxxxxxx
x[i-2] = 2222222...2222222 + yyyyyyyyyy
x[i] = 2222222...2222222 + yyyyyyyyyy + 1111111...1111111 + xxxxxxxxxx
/111...111
部分(当然这些不是实际字符)是 92 个字符长,那么你不需要剩下的东西 222...222
和 {{ 1}} 之后,因为无论如何您都无法使用有限的 xxxxx...
值访问它们。所以对于你的问题,
yyyyy...
与
相同k
足够高的x[i] = 2222222...2222222 + yyyyyyyyyy + 1111111...1111111 + xxxxxxxxxx
。
现在剩下的问题是计算/选择在计算 x[i] = 2222222...2222222
甚至 i
之类的东西时应该使用 111..111
或 222...222
的哪个序列。最有可能的情况类似于奇数/偶数检查,您可以在其中编写如下内容:“当 x[24]
为偶数时,使用 x[80]
,否则使用 n
。”。
¹) 检查是否存在逐一错误,92 个字符的阈值可能不在索引 x[10]
处。
有一个解决方案适用于任何 k
,它是一个 int
并且不包含昂贵的串联操作,具有 O(1)
内存和 O(log(k))
时间1.
前缀和奇偶校验
该算法使用@Progman 在他们的答案中所做的观察,即如果 a < b
和 a
和 b
具有相同的 parity,则 x[a]
是 x[b]
的前缀(根据 x[n-2]
是 x[n]
的前缀这一事实得出结论)。这意味着我们不需要计算序列中的 n
项,我们只需要找到 j
,我将其定义为最小数,使得 x[j]
的长度为大于 k
,并且 j
与 n
具有相同的奇偶校验。
例如,如果n = 12345
和k = 1
,那么我们只需要计算最多x[3] = 101
,因为我们知道x[3]
是x[12345]
的前缀因为 3
和 12345
都是奇数。所以答案是0
。
进入O(1)
记忆
用于避免存储零和一长序列的方法如下:
无需计算x
首先注意单词 x[n]
的长度等于 fib[n]
,其中 fib
是斐波那契数列。因此,该方法不是计算 x
中的字符串并索引到 x[n]
以查找是返回 1
还是 0
,而是使用 x[n] = x[n-2] + x[n-1]
的事实。您可以通过将 x[n][k]
与 x[n-2]
的长度(其中 x[n-1]
是 k
)。经过这个比较,就知道x[n-2]
是等于x[n-2]
还是fib[n-2]
。然后我们重复这个过程,将 x[n][k]
适当地设置为 x[n-2][k]
或 x[n-1][k-fib[n-2]]
,而 n
保持不变或适当地设置为 n-1
。如此重复直到 n-2
或 k
,此时 k-fib[n-2]
将是 n == 0
,所以 n == 1
要么等于 k
或 {{1} },根据定义。
无需存储0
计算中不需要x[n][k]
,只需要x[0][0] = 0
,这样可以避免存储很长的数字序列,但是我们肯定需要存储最多x[1][0] = 1
的所有斐波那契数,以便执行上一段中定义的步骤?不我们没有!这是因为我们首先找到了 fib
,只在内存中保留了 x
和 fib
。然后我们重新排列方程以找到 fib[j]
,并使用它来回溯斐波那契数列以找到 j
。
实施
现在我已经解释了算法,下面是一个 Java 实现:
首先我们定义一个 fib[i-1]
类来封装斐波那契数列,保持代码简洁。 (如果你需要坚持一个文件,你可以把它移到一个内部类。)
fib[i]
那么,实际算法:
fib[n-2] = fib[n] - fib[n-1]
去x[n][k]
或回家
Fib
时间复杂度意味着它运行得非常快,即使对于非常大的 class Fib {
private long a = 0;
private long b = 1;
private int index = 0;
void advance() {
long sum = a + b;
a = b;
b = sum;
index++;
}
void backtrack() {
long diff = b - a;
b = a;
a = diff;
index--;
}
long getPreviousValue() {
return a;
}
long getCurrentValue() {
return b;
}
int getIndex() {
return index;
}
}
值也是如此。如果您希望 public class Main {
public static int fibNK(int n,int k) {
Fib fib = new Fib();
// if n is odd,go to the next fib so that fib.getIndex() is 1
// this ensures that n and fib.getIndex() are either both even or both odd
if (n % 2 == 1) {
fib.advance();
}
// find the first fibonacci number greater than k that is still even/odd
while (k >= fib.getCurrentValue()) {
// x+2 is even if x is even,so advance twice
fib.advance();
fib.advance();
}
// now to find character k of the word:
// if we're looking at the first or second fibonacci word,"0" or "1",// then the character at index k must be 0 or 1
while (fib.getIndex() > 1) {
// only fib[i] and fib[i-1] are stored,but fib[i-2] is needed,so backtrack
fib.backtrack();
// we are trying to find fibWord[i][k]
// fibWord[i][k] = fibWord[i-2] + fibWord[i-1]
// if k >= fibWord[i-2].length,then the target character is in the second part of the word,fibWord[i-1]
if (k >= fib.getPreviousValue()) {
// specifically,if k >= fib[i-2],then fibWord[i][k]==fibWord[i-1][k-fibWord[i-2].length]
k -= fib.getPreviousValue();
} else {
// otherwise,fibWord[i][k]==fibWord[i-2][k],so another backtrack is needed
fib.backtrack();
}
}
// return either 0 or 1
return fib.getIndex();
}
public static void main(String[] args) {
// test the algorithm by using to print the first few words in `x`,one letter at a time
Fib fib = new Fib();
for (int n = 0; n < 8; n++) {
for (int k = 0; k < fib.getCurrentValue(); k++) {
System.out.print(fibNK(n,k));
}
System.out.println();
fib.advance();
}
}
}
的值大于 BigInteger
(相当于 O(log(k))
的值大于 k
),您可以将 k
更改为 {{1 }} 但是这在计算斐波那契数时可能会导致溢出错误,因此您需要将一些变量更改为BigInteger
,尽管这会略微增加时间和空间复杂度。
1 斐波那契数列有一个exponential lower bound,所以Integer.MAX_VALUE
的长度大于n
,也就是说45
斐波那契数列需要计算以找到大于 k
的一个。然后第二阶段执行相同数量的“回溯”操作以返回到 long
或 x[n]
,这是额外的 (3/2)**n
时间,总共产生 O(log(k))
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。