I was doing the google foobar challenge but ran out of time on the following challenge i am trying to see what i did wrong.
Challenge
As
For a one pass approach
Denote p_0,p_1,...,p_n
the positions of the pegs.
Let me define a sequence a_k
by the following recurrence relation
a_0 = 0
a_k = a_{k-1}+(-1)^{k+1}(p_k-p_{k-1})
If you compute a_n
and simplify you see that this is the same alternating sum of p_k
that you see in other answers in which most terms have a coefficient 2
except the first and the last one.
We will see below why it could be convenient to look at this sequence of numbers instead.
If we denote the radii of the gears by r_0,r_1,...,r_n
, then they satisfy the equations
r_k = (-1)^k(r_0-a_k)
Also, the condition that the radii are not smaller than 1
is equivalent to the inequalities
r_0 >= a_0 + 1
r_1 <= a_1 - 1
r_2 >= a_2 + 1
r_3 <= a_3 - 1
...
This means that the full sequence of inequality conditions for the radii can be reduced to the single pair of inequalities
max(a_k+1, for k even) <= r_0 <= min(a_k - 1, for k odd)
Finally, the condition of doubling the speed is
(1+2(-1)^{n+1}) r_0 = 2a_n(-1)^{n+1}
So, computing the sequence a_n
allows us to get both the answer and at the same time the restrictions on the radii in one pass.
Written in Python the code could look as follows. Feel free to improve it further.
define solution(pegs):
n = len(pegs)
if n<2:
return (-1,-1)
an = 0 # This contains, at each step the value of the sequence a_k
pm_one = -1 # The alternating sign in the formulas above.
# This and the next will get the bounds for the radii.
max_even_a = -float("inf")
min_odd_a = float("inf")
for i in range(n-1):
an -= pm_one*(pegs[i+1]-pegs[i])
pm_one *=-1
if not i&1:
min_odd_a = min(min_odd_a, an)
else:
max_even_a = max(max_even_a, an)
# In the formulas above the numerator has a (-1)^{n+1} factor.
# Here the sign has been cancelled with the sign of the denominator.
numerator = 2*an
denominator = abs(1+2*pm_one)
# The inequalities in the explanation are here written as integers.
# Note that here denominator is positive. So, passing it to the other side
# doesn't change the sign of the inequality.
# Of course, the inequalities have here the negated sign and an OR
# because we are detecting when they fail.
if numerator < denominator*(max_even_a+1) \
or numerator > denominator*(min_odd_a-1):
return (-1,-1)
# Sometimes the denominator is 3. If it can be cancelled we do so.
if pm_one == 1 and numerator%3 == 0:
numerator //=3
denominator = 1
return (numerator, denominator)
from fractions import Fraction
def answer(a):
l = len(a)
if(not a or l == 1): return [-1,-1]
s = (a[l-1] - a[0]) if (l % 2 == 0) else (-a[l-1]-a[0]);
if(l > 2):
for i in range(1, l-1): s+= 2 * (-1)**(i+1) * a[i]
v = Fraction(2*(float(s)/3 if (l%2==0) else float(s))).limit_denominator();
c = v;
for i in range(0, l-2):
d = a[i+1] - a[i]
n = d - c
if(c < 1 or n < 1): return [-1,-1]
else: c = n
return [v.numerator, v.denominator];
My python code uses fairly basic operations to get things done without brute-forcing it. However I'm lazy and didn't really comment, so you're gonna have to figure it out yourself. It passed the foobar solution thing, so it definitely works.
def answer(pegs):
distances = [pegs[x+1]-pegs[x] for x in range(len(pegs)-1)]
x = 0
for i in distances: #gets d_(n)-d_(n-1)+d_(n-2)...+-d_(1)
x = i - x
#this tests if firstGearRadius is positive or negative
if len(distances)%2 == 0: #if it's positive
solution = [x*-2,1]
elif len(distances)%2 == 1: #if it's negative
if x*2 % 3 == 0: #if the numerator is divisible by 3
solution = [x*2/3,1]
else:
solution = [x*2,3]
#finds sizes of the gears
gearSizes = [float(solution[0])/float(solution[1])]
x = gearSizes[0]
for i in distances:
x = i - x
gearSizes.append(x)
if any([True for x in gearSizes if x<=1]): #gears must be at least 1 unit radius
return [-1,-1]
else:
return solution
Here's the working code in python 2.7 for which all the test cases were passed by Google. This is the best solution that I came up with after scratching papers for a while:
from fractions import Fraction
def answer(pegs):
arrLength = len(pegs)
if ((not pegs) or arrLength == 1):
return [-1,-1]
even = True if (arrLength % 2 == 0) else False
sum = (- pegs[0] + pegs[arrLength - 1]) if even else (- pegs[0] - pegs[arrLength -1])
if (arrLength > 2):
for index in xrange(1, arrLength-1):
sum += 2 * (-1)**(index+1) * pegs[index]
FirstGearRadius = Fraction(2 * (float(sum)/3 if even else sum)).limit_denominator()
# now that we have the radius of the first gear, we should again check the input array of pegs to verify that
# the pegs radius' is atleast 1.
# since for valid results, LastGearRadius >= 1 and FirstGearRadius = 2 * LastGearRadius
# thus for valid results FirstGearRadius >= 2
if FirstGearRadius < 2:
return [-1,-1]
currentRadius = FirstGearRadius
for index in xrange(0, arrLength-2):
CenterDistance = pegs[index+1] - pegs[index]
NextRadius = CenterDistance - currentRadius
if (currentRadius < 1 or NextRadius < 1):
return [-1,-1]
else:
currentRadius = NextRadius
return [FirstGearRadius.numerator, FirstGearRadius.denominator]
See this image for how I came up with this code:
I think your solution is along the right lines, but doesn't allow for a fractional radius.
Note that we can consider your algorithm symbolically, setting g[0]=x
, and then computing all the g[j]
values in terms of x
. It turns out that each g[j]
is a linear function of x
(with gradient 1 or -1).
You will therefore find that g[-1] = a+mx
where m is +1 or -1, and a is an integer.
For a solution to exist you need to solve the equation:
g[0]/g[-1] = 2
x/(a+mx) = 2
x=2(a+mx)
x(1-2m)=2a
x=2a/(1-2m)
so this gives a candidate value of x (as a fraction) which you can then recheck to make sure that no intermediate radius went negative.
After evaluating most of the solutions in this Q&A, I have put calculated the results.
Gist for coding logic for evaluating correct implementations(mostly correct, but has issues) and performance
Solutions evaluated with links, and time of implementations(since people are finding faster and faster solutions over time). All solutions were verified excepted for ThisWind's solution
Number of test iterations: 10000
solutionValDo : 7.3772001
solutionThisWind : 1.1203797
solutionNotDijkstra : 0.3143226
salutionLamichhane : 6.6052445
solutioncbarraford : 26.4765467
solution1lann : 147.5525556
solutionDayz : 6.619154