问题
一系列正有理数定义如下:
由正有理数标记的无限完整二叉树是 定义如下:
- 根的标签是1/1
- 标签p / q的左子项是p /(p + q)
- 标签p / q的正确子女是(p + q)/ q
树的顶部如下图所示:
通过执行级别顺序(广度优先)来定义序列 遍历树(由浅色虚线表示)。那样:
F(1)= 1/1,F(2)= 1/2,F(3)= 2/1,F(4)= 1/3,F(5)= 3/2,F( 6)= 2/3,...
编写一个程序,找到其中F(n)为p / q的n的值 输入p和q。
输入
第一行输入包含一个整数P,(1≤P≤1000),其中 是后面的数据集的数量。每个数据集都应该是 相同和独立地处理。每个数据集由一个 单行输入。它包含数据集编号K,单个 空间,分子,p,正斜杠(/)和分母q, 期望的分数。
输出
对于每个数据集,只有一行输出。它包含 数据集编号K,后跟一个空格,然后跟随 通过n的值,其中F(n)是p / q。输入将被选择为n 将适合32位整数。
我的方法
我创建堆并计划迭代它直到我找到有问题的元素,但是我的内存不足以至于我很确定我应该这样做而不创建堆积了什么?
代码
public ARationalSequenceTwo() {
Kattio io = new Kattio(System.in, System.out);
StringBuilder sb = new StringBuilder(10000);
int iter = io.getInt();
// create heap
int parent;
Node[] heap = new Node[Integer.MAX_VALUE];
int counter = 1;
heap[0] = new Node(1, 1);
while (counter < Integer.MAX_VALUE) {
parent = (counter - 1) / 2;
// left node
heap[counter++] = new Node(heap[parent].numerator, heap[parent].numerator + heap[parent].denominator);
// right node
heap[counter++] = new Node(heap[parent].numerator + heap[parent].denominator, heap[parent].denominator);
}
// find Node
int dataSet;
String word;
int numerator;
int denominator;
for (int i = 0; i < iter; i++) {
dataSet = io.getInt();
word = io.getWord();
numerator = Integer.parseInt(word.split("/")[0]);
denominator = Integer.parseInt(word.split("/")[1]);
for (int j = 0; j < Integer.MAX_VALUE; j++) {
Node node = heap[j];
if (node.numerator == numerator && node.denominator == denominator) {
sb.append(dataSet).append(" ").append(j).append("\n");
}
}
}
System.out.println(sb);
io.close();
}
答案 0 :(得分:2)
让我们考虑节点n = a / b。如果n是其父亲的左子,则n = p /(p + q),其中父亲是p / q。即
p = a,
b = p + q
p = a,
q = b - a
如果n是其父级的右子级,则n =(p + q)/ q:
a = p + q
b = q
p = a - b =
q = b
所以,例如3/5,它是左孩子还是右孩子?如果它是一个左孩子,那么它的父母将是3 /(5-3)= 3/2。对于合适的孩子,我们会有(3-5)/ 5 = -2/5。由于这不是积极的,显然n是左孩子。
所以,概括:
给定理性n,我们可以找到根的路径如下:
ArrayList lefts = new ArrayList&lt;&gt;();
while (nNum != nDen) {
if (nNum < nDen) {
//it's a left child
nDen = nDen - nNum;
lefts.add(true);
} else {
nNum = nNum - nDen;
lefts.add(false);
}
}
现在我们在数组中有路径,我们如何在最终结果中翻译它?我们来看看
我们留下了最后一块,即将节点添加到我们拥有的最后一个节点的左侧,一个节点对应于输入的理由,加上一个节点。左边有多少个节点?如果你试图标记每个弧向左移动0并且每个弧向右移动1,你会注意到路径以二进制形式表示最后一级中的节点数。例如,3/5是3/2的左子。数组将填充false,true,false。二进制,010。最终结果为2 ^ 0 + 2 ^ 1 + 2 ^ 2 + 010 + 1 = 1 + 2 + 4 + 2 + 1 = 10
最后,请注意sum(2 ^ i)是2 ^(i + 1) - 1.因此,我们最终可以编写第二部分的代码:
int s = (1 << lefts.size()) - 1) // 2^(i+1) - 1
int k = 0
for (int i = lefts.size() - 1; i >= 0; i---) {
if (lefts.get(i)) {
k += 1 << i;
}
}
return s + k + 1;
输入num和den的完整程序:
import java.util.ArrayList;
public class Z {
public static int func(int num, int den) {
ArrayList<Boolean> lefts = new ArrayList<>();
while (num != den) {
if (num < den) {
//it's a left child
den = den - num;
lefts.add(true);
} else {
num = num - den;
lefts.add(false);
}
}
int s = (1 << lefts.size()) - 1; // 2^(i+1) - 1
int k = 0;
for (int i = lefts.size() - 1; i >= 0; i--) {
if (!lefts.get(i)) {
k += 1 << i;
}
}
return s + k + 1;
}
public static void main(String[] args) {
System.out.println(func(Integer.parseInt(args[0]),
Integer.parseInt(args[1])));
}
}
答案 1 :(得分:2)
给定p / q数,你可以通过考虑是否p> 1来看出它是否是其父母的左或右孩子。 q或p < Q值。并且可以在树上一直重复该过程回到根。
这给出了一个相对简单的递归解决方案。在伪代码中:
T(p, q) =
1 if p == q == 1
2 * T(p, q-p) if p < q
2 * T(p-q, q) + 1 if p > q
理论上这可能导致堆栈溢出,因为它在O(p + q)时间和空间中运行。例如,T(1000000,1)将需要100万个递归调用。但是在问题中给出了T(p,q)&lt; 2 ** 31,所以树的深度最多可以是32,这个解决方案效果很好。