Skip to content

Commit 3124931

Browse files
committed
Project euler problem 66 complete
1 parent 6c04620 commit 3124931

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

project_euler/problem_066/__init__.py

Whitespace-only changes.

project_euler/problem_066/sol1.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"""
2+
Project Euler Problem 61: https://projecteuler.net/problem=66
3+
4+
Problem statement-
5+
Consider quadratic Diophantine equations of the form:
6+
x^2 - D * y^2 = 1
7+
8+
For example, when D = 13 , the minimal solution in x is 649^2 - 13* 180^2 = 1.
9+
10+
It can be assumed that there are no solutions in positive integers when D is square.
11+
12+
By finding minimal solutions in x for D = {2, 3, 5, 6, 7} , we obtain the following:
13+
3^2 - 2 * 2^2 = 1
14+
2^2 - 3 * 1^2 = 1
15+
9^2 - 5 * 4^2 = 1 (highest value of x)
16+
5^2 - 6 * 2^2 = 1
17+
8^2 - 7 * 3^2 = 1
18+
19+
Hence, by considering minimal solutions in x for D<= 7 ,
20+
the largest x is obtained when D = 5.
21+
22+
Find the value of D<=1000 in minimal solutions of x for
23+
which the largest value of is obtained.
24+
25+
26+
Solution explanation -
27+
The above given equation is known as Pell's equation. The solution uses the standard
28+
method for solving pell's equation. Which is available on the wikipedia page.
29+
The solution uses the continued fraction method for finding close approximations
30+
of sqrt(D) which are of the form h/k. Because for these solutions the h and k are
31+
solutions for x and y respectively. But we dont want an exact approximation because
32+
there is a 1 on the RHS. We consider upto 2 periodic cycles, which guarantee a
33+
solution. We check in the continued fraction considering newer fractions and checking
34+
if numerator(x) and denominator(y) satisfy the diophantine equation.
35+
36+
37+
References-
38+
Wikipedia - https://en.wikipedia.org/wiki/Pell%27s_equation
39+
40+
41+
"""
42+
43+
import math
44+
45+
46+
def is_perfect_square(num: int) -> bool:
47+
"""
48+
Checks if the number inputted is a perfect square or not.
49+
Returns True if perfect square else False.
50+
51+
>>> is_perfect_square(25)
52+
True
53+
>>> is_perfect_square(3)
54+
False
55+
56+
Time complexity - O(log2(num))
57+
58+
"""
59+
60+
if num < 0:
61+
return False
62+
root = math.isqrt(num)
63+
return root * root == num
64+
65+
66+
def continued_fraction(num: int) -> list[int]:
67+
"""
68+
Computes and returns the continued fraction of the square root of the given integer.
69+
Note for irrational numbers, the continued fraction stops when there is repetition.
70+
The returned list is of the form -> [a0;a1,a2,a3...an]
71+
where a1,a2,a3...an represents the minimumperiodic part of the continued fraction.
72+
73+
Time complexity - O(sqrt(num))
74+
75+
>>> continued_fraction(2)
76+
[1, 2]
77+
>>> continued_fraction(13)
78+
[3, 1, 1, 1, 1, 6]
79+
"""
80+
81+
ans = []
82+
83+
# Initialize variables for continued fraction
84+
m = 0
85+
d = 1
86+
a0 = math.isqrt(num)
87+
88+
# this is the end point from where the sequence starts to repeat
89+
terminate_point = 2 * a0
90+
91+
ans.append(a0)
92+
an = a0
93+
94+
# loop till termination point is reached, storing each an value
95+
while an != terminate_point:
96+
m = d * an - m
97+
d = (num - m**2) / d
98+
an = math.floor((a0 + m) / d)
99+
ans.append(an)
100+
101+
return ans
102+
103+
104+
def diophantine_equation(var_x: int, var_y: int, var_d: int) -> int:
105+
"""
106+
Returns the LHS of the diophantine equation using x, y and D.
107+
108+
>>> diophantine_equation(3, 2, 2)
109+
1
110+
>>> diophantine_equation(8, 3, 7)
111+
1
112+
>>> diophantine_equation(3, 4, 5)
113+
-71
114+
"""
115+
116+
return var_x**2 - var_d * var_y**2
117+
118+
119+
def solution(max_d: int = 1000) -> int:
120+
"""
121+
This function provides a solution to the problem 66 from project Euler.
122+
The original problem is for D<= 1000. But there is a optional parameter
123+
max_d, by changing it you can find solutions for any D theoretically.
124+
125+
Time complexity - O(max_d * (log2(max_d) + sqrt(max_d)))
126+
But since sqrt(max_d) dominates log2(max_d)
127+
Final time complexity is - O(max_d^3/2)
128+
129+
>>> solution()
130+
661
131+
>>> solution(10000)
132+
9949
133+
"""
134+
135+
# Initialise answer which stored max value of D.
136+
answer = -1
137+
# Initialise max_x which stores max value of x.
138+
max_x = -1
139+
140+
# loop over all values of D
141+
for d in range(2, max_d + 1):
142+
# skip if d is prefect square
143+
if is_perfect_square(d):
144+
continue
145+
146+
# Get continued fraction and then
147+
# Add the periodic part of the fraction to itself because we are
148+
# guaranteed to find the solution in 2 cycles of the periodic part.
149+
# example -
150+
# fraction = [3,1,1,2]
151+
# First element is the integer part, rest is periodic fractional part.
152+
# Add the fractional part on itself.
153+
# fraction becomes [3,1,1,2,1,1,2].
154+
# This contains our solution for sure.
155+
fraction = continued_fraction(d)
156+
fraction += fraction[1:]
157+
158+
num_0 = 0
159+
num_1 = 1
160+
den_0 = 1
161+
den_1 = 0
162+
163+
# This is a recursive relation to find the next numerator and denominator
164+
num_2 = fraction[0] * num_1 + num_0
165+
den_2 = fraction[0] * den_1 + den_0
166+
167+
n = 1
168+
# Use the recursive relation to get new, more accurate numerator and
169+
# denominator, then check if they satisfy the given diophantine equation.
170+
while diophantine_equation(num_2, den_2, d) != 1:
171+
num_0 = num_1
172+
num_1 = num_2
173+
num_2 = fraction[n] * num_1 + num_0
174+
175+
den_0 = den_1
176+
den_1 = den_2
177+
den_2 = fraction[n] * den_1 + den_0
178+
179+
n += 1
180+
181+
# If we see a new larger value of x, we store the corresponding D
182+
# value as our answer.
183+
if num_2 > max_x:
184+
answer = d
185+
max_x = num_2
186+
187+
return answer
188+
189+
190+
if __name__ == "__main__":
191+
print(f"{solution(10000) = }")

0 commit comments

Comments
 (0)