我发现了与有理数相关的问题。
给出了两个有理数,任务是找到它们之间最简单的有理数。
对于这个问题,有理数的简单性可以定义为具有最小分子的有理数,尽管我对这个度量的其他建议持开放态度,例如: similar question to Math stack exchange,如果它使解决方案更容易。
样本输入和输出可能是:
Inputs: 1110/416 and 1110/417, Output: 8/3
Inputs: 500/166 and 500/167, Output: 3/1
有关如何解决此问题的任何想法或至少建议?我很挣扎。
由于
编辑:
补充意见:
答案 0 :(得分:5)
关于continued fractions的维基百科文章中描述了相关数学。简而言之,您计算下端点和上端点的两个连续分数,然后尝试四个组合,其中连续分数在公共端点之后被截断。
这是一个Python实现。
import fractions
F = fractions.Fraction
def to_continued_fractions(x):
a = []
while True:
q, r = divmod(x.numerator, x.denominator)
a.append(q)
if r == 0:
break
x = F(x.denominator, r)
return (a, a[:-1] + [a[-1] - 1, 1])
def combine(a, b):
i = 0
while i < len(a) and i < len(b):
if a[i] != b[i]:
return a[:i] + [min(a[i], b[i]) + 1]
i += 1
if i < len(a):
return a[:i] + [a[i] + 1]
if i < len(b):
return a[:i] + [b[i] + 1]
assert False
def from_continued_fraction(a):
x = fractions.Fraction(a[-1])
for i in range(len(a) - 2, -1, -1):
x = a[i] + 1 / x
return x
def between(x, y):
def predicate(z):
return x < z < y or y < z < x
return predicate
def simplicity(x):
return x.numerator
def simplest_between(x, y):
return min(filter(between(x, y), (from_continued_fraction(combine(a, b)) for a in to_continued_fractions(x) for b in to_continued_fractions(y))), key=simplicity)
print(simplest_between(F(1110, 416), F(1110, 417)))
print(simplest_between(F(500, 166), F(500, 167)))
答案 1 :(得分:3)
如果有一些分母在你的输入之间产生一个有理数,那么假设分子是好的。
您可以检查O(1)中的分子是否良好。假设您要检查分子n,输入为w,x(对于w / x)和y,z(对于y / z)。
如果n x / w和n z / y之间存在整数,则n是好的。
然后,你可以通过检查所有分子直到找到一个好的分子来在O(好的分子)中做到这一点。如果端点有效,则最多需要min(w,y)。
答案 2 :(得分:1)
这是上面David Eisenstat出色的answer的一个变体。秘密地,它基于找到区间端点连续分数扩展的共同初始部分的完全相同的原理,但是从它的编码方式来看这并不明显,并且无需给出参考即可直接给出正确性证明连续分数理论。该证明的草图在下面进一步给出。
提醒一下,目标是找到给定间隔中最简单的分数。在这里,最简单具有特定的含义(并且很强):我们说分数x = s/t
比分数y = u/v
更简单(两者都如果用abs(s) <= abs(u)
和 t <= v
和中的至少两个不等式中的至少一个是严格的,则用最小的术语来表示。请注意,使用这个定义 simpler 不会引起总排序:2/5
或3/4
的分数都不比另一个更简单;尽管如此,这是一个(不立即显而易见的)定理,即包含至少一个分数的实线的任何子区间都包含一个最简单的分数,该分数比该子区间中的所有其他分数更简单。
事不宜迟,以下是我们的simplest_between
版本的一些Python代码。接下来是解释和正确性证明的草图。
def simplest_between(x: Fraction, y: Fraction) -> Fraction:
"""
Simplest fraction strictly between fractions x and y.
"""
if x == y:
raise ValueError("no fractions between x and y")
# Reduce to case 0 <= x < y
x, y = min(x, y), max(x, y)
if y <= 0:
return -simplest_between(-y, -x)
elif x < 0:
return Fraction(0, 1)
# Find the simplest fraction in (s/t, u/v)
s, t, u, v = x.numerator, x.denominator, y.numerator, y.denominator
a, b, c, d = 1, 0, 0, 1
while True:
q = s // t
s, t, u, v = v, u - q * v, t, s - q * t
a, b, c, d = b + q * a, a, d + q * c, c
if t > s:
return Fraction(a + b, c + d)
代码的第一部分(简化为0 <= x < y
的情况)应该是不言自明的:如果间隔(x, y)
完全位于
负实数,我们使用关于零的对称性并找到最简单的分数
(-y, -x)
中,然后取反。否则,如果间隔(x, y)
包含零,则0/1
是(x, y)
中最简单的分数。否则,(x, y)
位于正实数之内,我们继续进行代码的第二部分。
第二部分是它变得更有趣的地方。在算法的每一步:
s
,t
,u
和v
是非负整数,它们描述一个
正实线(J = (s/t, u/v)
的子间隔v
可以为零,因此u/v
表示一个无限端点。a
,b
,c
和d
是非负整数,它们描述一个
线性分数变换T : z ↦ (az + b) / (cz + d)
。T
给出J
与原始间隔(x, y)
之间的双射。ad-bc = ±1
(符号在每次迭代中交替出现)最初,J = (s/t, u/v)
是原始间隔(x, y)
,而T
是原始间隔
身份转换(由a = d = 1
,b = c = 0
提供)。 while循环用间隔J
重复替换1/(J - q)
,其中q
是J
左端点的底,并同时更新a
,{{ 1}},b
和c
以便在两者之间保持双射d
T
和J
。
只要间隔(x, y)
包含J
,循环就会退出。终止
保证了循环:和1
是一个正整数,每次迭代都会严格减少,但第一次迭代可能会例外(其中s + t + u + v
可以是q
)。
在循环退出时,0
中每个分数(x, y)
的形式为(ap + bq)/(cp + dq)
,p/q
中某个分数gcd(p, q) = 1
(带有J
);此外,表达式(ap + bq)/(cp + dq)
的用语最低:它与gcd(p, q) = 1
一起来自ad - bc = ±1
。由于a
,b
,c
和d
均为非负数,因此(a+b)/(c+d)
是(x, y)
中最简单的分数。
与David的答案一样,simplest_between
总是在给定端点之间严格严格地产生分数。下一个变体非常相似,但是在给定的 closed 间隔内产生最简单的分数
改为[x, y]
。
def simplest_between_lax(x: Fraction, y: Fraction) -> Fraction:
"""
Simplest fraction between fractions x and y, inclusive of x and y.
"""
# Reduce to case 0 < x <= y
x, y = min(x, y), max(x, y)
if y < 0:
return -simplest_between_lax(-y, -x)
elif x <= 0:
return fractions.Fraction(0, 1)
# Find the simplest fraction in [s/t, u/v]
s, t, u, v = x.numerator, x.denominator, y.numerator, y.denominator
a, b, c, d = 1, 0, 0, 1
while True:
q = (s - 1) // t
s, t, u, v = v, u - q * v, t, s - q * t
a, b, c, d = b + q * a, a, d + q * c, c
if t >= s:
return fractions.Fraction(a + b, c + d)
以下是OP的输入示例:
>>> F = fractions.Fraction
>>> simplest_between(F(1110, 416), F(1110, 417))
Fraction(8, 3)
>>> simplest_between(F(500, 166), F(500, 167))
Fraction(3, 1)
闭区间版本会产生相同的结果,当然:
>>> simplest_between_lax(F(1110, 416), F(1110, 417))
Fraction(8, 3)
>>> simplest_between_lax(F(500, 166), F(500, 167))
Fraction(3, 1)
但是simplest_between_lax
允许考虑端点:
>>> simplest_between(3, 4)
Fraction(7, 2)
>>> simplest_between_lax(3, 4)
Fraction(3, 1)
>>> simplest_between(F(7, 6), F(6, 5))
Fraction(13, 11)
>>> simplest_between_lax(F(7, 6), F(6, 5))
Fraction(6, 5)
答案 3 :(得分:0)
您可以尝试以下O(n^2 log n)
算法: