Google foobar gearing_up_for_destruction

时间:2016-11-07 13:00:51

标签: python algorithm

我正在做谷歌foobar挑战,但在以下挑战上没时间我试图看看我做错了什么。

  

挑战

     

作为Lambda指挥官的私人助理,您已经完成了配置LAMBCHOP世界末日设备轴向齿轮的任务。它应该非常简单 - 只需添加齿轮即可创建合适的旋转比率。但问题是,由于LAMBCHOP的布局以及支撑它的梁和管道的复杂系统,支撑齿轮的销钉固定到位。

     

LAMBCHOP的工程师已经为您列出了识别各种支撑梁上钉子组的位置的列表。您需要在每个挂钉上放置一个齿轮(否则齿轮会与未占用的挂钉碰撞)。工程师拥有大量不同尺寸的齿轮,因此您可以选择任意尺寸的齿轮,从半径为1。您的目标是建立一个系统,其中最后一个齿轮以第一档的速率(每分钟转数或转速)的两倍旋转,无论方向如何。每个齿轮(最后一个齿轮除外)接触并转动下一个挂钉的齿轮。

     

给定一个名为pegs的不同正整数列表,表示每个peg沿支撑梁的位置,写一个函数答案(pegs),如果有解,则返回两个正整数的列表a和b表示为了实现上述目标,第一档半径的分子和分母以其最简单的形式,使得半径= a / b。比率a / b应大于或等于1.并非所有支持配置都必须能够创建正确的旋转比率,因此如果任务不可能,则函数answer(pegs)应返回列表[-1, -1]。

     

例如,如果钉位于[4,30,50],则第一个齿轮的半径可以为12,第二个齿轮的半径可以为14,最后一个齿轮的半径为6。因此,最后一个齿轮的旋转速度是第一个齿轮的两倍。在这种情况下,钉子将是[4,30,50]并且答案(钉子)应该返回[12,1]。

     

列表挂钩将按升序排序,并包含至少2个且不超过20个不同的正整数,所有正整数均在1和10000之间。

测试用例

Inputs:
(int list) pegs = [4, 30, 50]
Output:
(int list) [12, 1]

Inputs:
(int list) pegs = [4, 17, 50]
Output:
(int list) [-1, -1]

我目前的解决方案如下

def answer(pegs):
    n = len(pegs)
    g = range(n)
    k = pegs[1] - pegs[0]
    for i in range(0,k,2):
        g[0] = i
        for j in range(1,n):
            g[j] = (pegs[j] - pegs[j-1]) - g[j-1]   
        if any(b < 1 for b in g):
            continue
        if 1.0*g[0]/g[-1] == 2.0:
            return [g[0],1]
    return [-1, -1]

我只能通过6个测试用例我现在已经没时间了,但我很好奇什么是正确的解决方案

12 个答案:

答案 0 :(得分:8)

这是python 2.7中的工作代码,所有测试用例都由Google传递。这是我在抓论一段时间之后提出的最佳解决方案:

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.

    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]

请参阅此图片,了解我如何提出此代码:

Image

答案 1 :(得分:4)

我认为您的解决方案是沿着正确的方向行进,但不允许小数半径。

请注意,我们可以象征性地考虑您的算法,设置g[0]=x,然后根据g[j]计算所有x值。事实证明,每个g[j]x的线性函数(渐变1或-1)。

因此,您会发现g[-1] = a+mx其中m为+1或-1,a为整数。

对于存在的解决方案,您需要解决方程式:

g[0]/g[-1] = 2
x/(a+mx) = 2
x=2(a+mx)
x(1-2m)=2a
x=2a/(1-2m)

所以这给出了一个候选值x(作为分数),然后你可以重新检查它以确保没有中间半径变为负数。

答案 2 :(得分:3)

如果您对完美的工作解决方案感兴趣,这就是我写的:https://gist.github.com/1lann/be45311db1bd8cbbe6650b0a3e9d1977

它构造了一个方程组,它解决了每个齿轮每个半径的值。以下是它如何计算4个桩的解决方案。

方程组将是:

2x + a = peg[1] - peg[0]
a + b = peg[2] - peg[1]
b + x = peg[3] - peg[2]

我的程序构造一个矩阵来表示:

[
    [2, 1, 0],
    [0, 1, 1],
    [1, 0, 1]
]

然后计算矩阵的倒数,然后将其应用于桩钉之间的距离,以便找到每个齿轮的半径。如果您想知道数学如何工作,您可以查看:https://www.mathsisfun.com/algebra/systems-linear-equations-matrices.html

然后验证每个齿轮的半径> = 1,最后返回x * 2的值。为了支持分数(任何有理数),所有数字都是分数类型。

我对一些边缘情况进行了硬编码,例如当pegs的数量= 2时。

答案 3 :(得分:2)

单次通过方法

表示p_0,p_1,...,p_n钉的位置。 让我通过以下递归关系定义序列a_k

a_0 = 0
a_k = a_{k-1}+(-1)^{k+1}(p_k-p_{k-1})

如果您计算a_n并简化,您会发现这与p_k的交替总和与其他答案中看到的相同,在大多数答案中,除第一个和第二个词语外,大多数词语的系数为2。最后一个。

我们将在下面看到为什么可以方便地查看此数字序列。

如果我们用r_0,r_1,...,r_n表示齿轮的半径,则它们满足方程式

r_k = (-1)^k(r_0-a_k)

此外,半径不小于1的条件等于不等式

r_0 >= a_0 + 1
r_1 <= a_1 - 1
r_2 >= a_2 + 1
r_3 <= a_3 - 1
...

这意味着可以将半径不等式的全部序列简化为一对不等式

max(a_k+1, for k even) <= r_0 <= min(a_k - 1, for k odd)

最后,速度加倍的条件是

(1+2(-1)^{n+1}) r_0 = 2a_n(-1)^{n+1}

因此,计算序列a_n可以让我们同时获得答案和一遍中的半径限制。

用Python编写的代码看起来像as follows。随时进行进一步改进。

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)

答案 4 :(得分:2)

我在 2020 年 1 月遇到了这个问题。完成后,我想看看其他人是否按照我的方式进行了操作,但看起来不像。

销钉的位置以及第一个半径表示最后一个齿轮的半径是多少。第一个齿轮半径和最后一个齿轮半径之间存在线性关系,以及第一个齿轮半径大小的边界:

这是我编写的函数,它将接收销钉位置和起始齿轮半径并计算隐含的最后一个齿轮半径:

def implied_last_radius(pegs, start_radius):
    diffs = [start_radius] + [x - y for x, y in zip(pegs[1:], pegs[:-1])]
    for i in range(1, len(diffs)):
        diffs[i] = diffs[i] - diffs[i - 1]

    return diffs[-1]

问题指出所有齿轮的半径必须>= 1,最后一个齿轮必须是第一个齿轮大小的一半。这对半径为 2 的第一个齿轮设置了下限(即,第一个齿轮半径小于 2 的任何有效结果将导致最后一个齿轮半径小于 1,这是不允许的)。这也为第一个齿轮尺寸设置了上限,因为第二个挂钩中的齿轮也必须具有最小半径 1。因此:

first_radius_diff = pegs[1] - pegs[0]
first_radius_range = (2, first_radius_diff - 1)

我们可以使用上述函数计算每个第一个半径的隐含半径。对于挂钩 [4, 30, 50] 答案是:

First Radii = (2, 25)
Implied Last Radii = (-4, 19)

即如果第一个半径为2,则最后一个半径必须为-4(无效),如果第一个半径为25,则最后一个半径为19。线性关系可以建模为y = mx + b :

m = (implied_radii[1] - implied_radii[0]) / (first_radius_range[1] - first_radius_range[0])
b = implied_radii[0] - m * first_radius_range[0]

现在我们有了第一个和最后一个齿轮半径大小之间的关系。当最后一个半径是值的一半时,我们简单地计算第一个半径:

first_radius = b / (0.5 - m)

最后,我们检查该值是否在允许的第一齿轮半径范围内,但重要的是它不需要任何半径小于 1 的销钉上的齿轮:

def solvable(pegs, start_radius, radius_range):
    diffs = [start_radius] + [x - y for x, y in zip(pegs[1:], pegs[:-1])]
    for i in range(1, len(diffs)):
        diffs[i] = diffs[i] - diffs[i - 1]

    return radius_range[0] <= start_radius <= radius_range[1] and all([diff >= 1 for diff in diffs])

最后一个让我感到棘手的部分是转换为简化的小数形式。这很容易通过将所有数值映射到 fractions.Fraction 类来解决。如果第一齿轮半径通过可解测试,则返回分子和分母,否则返回 [-1, -1]。

答案 5 :(得分:0)

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];

答案 6 :(得分:0)

通过的解决方案:

from fractions import Fraction

def solution(p):
    n = len(p)

    if n >= 2:
        r0_n = -2 * (p[n - 1] + reduce(
            lambda a, b: a + b, [0] + [(-1)**i * 2 * p[i]
                                       for i in range(n - 2, 0, -1)]) + (-1)**(n-1)*p[0])

        r0_d = 1 + ((n+1) % 2)*2

        if r0_n < r0_d:
            return [-1, -1]

        r = ['NAN'] * n
        r[0] = float(r0_n) / float(r0_d)
        for i in range(1, n):
            r[i] = p[i] - p[i - 1] - r[i - 1]
            if r[i] < 1:
                return [-1, -1]

        r0 = Fraction(r0_n, r0_d)
        r0.limit_denominator()
        
        return [r0.numerator, r0.denominator]

    return [-1, -1]

一些测试

if __name__ == '__main__':
    print solution([4, 30, 50]), [12, 1]
    print solution([4, 17, 50]), [-1, -1]
    print solution([1, 51]), [100, 3]
    print solution([1, 31]), [20, 1]
    print solution([1, 31, 51, 71]), [20, 1]
    print solution([1, 31, 51, 71, 91]), [20, 1]
    print solution([4, 9, 17, 31, 40]), [4, 1] 

输出:

[12, 1] [12, 1]
[-1, -1] [-1, -1]
[100, 3] [100, 3]
[20, 1] [20, 1]
[20, 1] [20, 1]
[20, 1] [20, 1]
[4, 1] [4, 1]

一些思考

#    equaltion         | i
# ---------------------|---
# / r0 + r1 == p1 - p0  [0]
# | r1 + r2 == p2 - p1  [1]
# | r2 + r3 == p3 - p2  [2]
# | r3 + r4 == p4 - p3  [3]
# | r4 + r5 == p5 - p4  [4]
# \      r5 == r0/2     [5]
#
#
# / r0 + r1 + 0  + 0  + 0  + 0    = p1 - p0
# | 0  + r1 + r2 + 0  + 0  + 0    = p2 - p1
# | 0  + 0  + r2 + r3 + 0  + 0    = p3 - p2
# | 0  + 0  + 0  + r3 + r4 + 0    = p4 - p3
# | 0  + 0  + 0  + 0  + r4 + r5   = p4 - p4
# \ r0 + 0  + 0  + 0  + 0  - 2*r5 = 0
#
# / 1 1 0 0 0  0 \   / r0 \   / p1 - p0 \
# | 0 1 1 0 0  0 |   | r1 |   | p2 - p1 |
# | 0 0 1 1 0  0 |   | r2 |   | p3 - p2 |
# | 0 0 0 1 1  0 | * | r3 | = | p4 - p3 |
# | 0 0 0 0 1  1 |   | r4 |   | p5 - p4 |
# \ 1 0 0 0 0 -2 /   \ r5 /   \    0    /

答案 7 :(得分:0)

在评估了本问答中的大多数解决方案之后,我计算了结果。

Gist for coding logic for evaluating correct implementations(mostly correct, but has issues) and performance

通过链接和实现时间评估解决方案(因为随着时间的推移人们发现越来越快的解决方案)。除ThisWind解决方案外,所有解决方案均已通过验证

  • NotDijkstra (aka. franklinvp)-2020年6月19日,表现最轻松
  • Val Do-2018年2月16日
  • ThisWind-与此相关,并且无法通过验证(5次通过和5次失败)。就我而言,这可能是一个潜在的错误。
  • Lamichhane-2017年8月11日
  • cbarraford-2017年3月10日
  • 1lann-2016年11月11日
  • Dayz-2018年9月27日

效果结果

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 

答案 8 :(得分:0)

昨晚我收到了他们的邀请,我在这个问题上工作了几个小时。我创建了这个问题的解决方案。

from fractions import Fraction
from sympy import symbols, solve


def create_equations(_pegs, _isEven, smbls, r0):
    eq = None
    temp = symbols('temp')
    if _isEven:
        for i in range(len(smbls)):
            if i == 0:
                eq = -r0 - r0 / 2 - smbls[i] + temp
            if 0 < i < len(smbls):
                eq = eq.subs(temp, (-1) ** (i + 1) * 2 * smbls[i] + temp)
            if i == len(smbls) - 1:
                eq = eq.subs(temp, - smbls[i])
    else:
        for i in range(len(smbls)):
            if i == 0:
                eq = -r0 + r0 / 2 - smbls[i] + temp
            if 0 < i < len(smbls):
                eq = eq.subs(temp, (-1) ** (i + 1) * 2 * smbls[i] + temp)
            if i == len(smbls) - 1:
                eq = eq.subs(temp, smbls[i])
    return eq


def create_symbols(len_pegs):
    smbls = []
    for i in range(len_pegs):
        smbls.append(symbols("P" + str(i)))
    r0 = symbols("r0")
    return smbls, r0


def answer(pegs):

    # first check
    len_pegs = len(pegs)
    if (not pegs) or len_pegs == 1:
        return [-1, -1]

    isEven = True if (len_pegs % 2 == 0) else False

    # create list of symbols used in equation based on list length
    smbls, r0 = create_symbols(len_pegs)

    # the function returns an equation based on the following equation
    # for even list length : (0 = r0 + rn -Pn + 2Pn-1 - 2Pn-2 + 2Pn-3 ... - P1 + P0)
    # for odd list length : (0 = r0 - rn -Pn + 2Pn-1 - 2Pn-2 + 2Pn-3 ... + P1 - P0)
    #  where rn = r0/2
    equation = create_equations(pegs, isEven, smbls, r0)

    # substituting values of variables in the equation
    for i in range(len_pegs):
        equation = equation.subs(smbls[i], pegs[i])

    # solving the equation and simplifying float values to simple fraction
    radius_g1 = float(solve(equation)[0])
    radius_g1 = Fraction(radius_g1).limit_denominator()

    # finally we check if radius of any gear is less than one as required
    radius_cur = radius_g1
    for i in range(0, len_pegs-1):
        dist_c = pegs[i + 1] - pegs[i]
        radius_next = dist_c - radius_cur
        if radius_cur < 1 or radius_next < 1:
            return [-1, -1]
        else:
            radius_cur = radius_next

    # return the end results [numerator, denominator]
    return [radius_g1.numerator, radius_g1.denominator]


if __name__ == "__main__":
    # try some examples
    print(answer([4, 30, 50, 56]))
    print(answer([4, 17, 50]))

答案 9 :(得分:0)

在此处添加我的递归解决方案。


def solutionNader(pegs):
    
    """
        returns the radius of cog i
        ri = Xi -/+ r0
        also sets the ans for the fractional requirement.
    """
    def helper(X,i):
        if i == len(pegs)-1: # last cog, we need to make sure its half of r0
            X = 2*(pegs[i]-pegs[i-1]-X)
            if i%2:
                ans[0],ans[1] = [X,3] if (X)%3 else [X/3,1]
                return X/6.0
            else:
                ans[0],ans[1] = [-X,1]
                return -X/2
        #recursively calculate the radius of next cog
        r_next = helper(pegs[i]-pegs[i-1]-X,i+1) if i>0 else helper(0,1)

        #radius of r = gap bitween the pegs - r of next cog        
        r = pegs[i+1]-pegs[i]-r_next 

        if r < 1: 
            ans[0],ans[1] = [-1,-1]
            raise Exception('Invalid Cog')
        return r

    try:
        ans = [-1,-1]
        helper(0,0)
    finally:
        return ans

感谢@Ashitakalax 的性能测试

测试迭代次数:10000

  • solutionNotDijkstra:0.0879838466644
  • 解决方案纳德:0.119354009628
  • solutionThisWind:0.224207162857
  • solutionDayz:1.24098587036
  • salutionLamichhane : 1.32456707954

答案 10 :(得分:0)

这是我想出的解决方案,我在 2021 年 3 月 30 日做到了,并且通过了 verify solution.pysubmit solution.py

解决方案 (pegs.py)

import numpy as np
from fractions import Fraction


def solution(pegs):
    # constraint I: gear[i] + gear[i+1] == distance[i]
    # constraint II: gear[0] == 2 * gear[-1]
    # constraint III: gear[0] >= 1
    #
    # I ensures that the gears fit into the spaces of the pegs
    # II ensures that the total gear ration is 2:1
    # III ensures that the gears have at least a radius of 1

    pegs = np.array(pegs)

    n = len(pegs)
    gear_1 = [-1, -1]
    distances = np.zeros(n - 1)
    system_equations = np.zeros([n, n + 1])

    # calculate the distances
    for i in range(n - 1):
        distances[i] = pegs[i + 1] - pegs[i]

    # The first equation is about constraint II
    system_equations[0, 0] = 1
    system_equations[0, -2] = -2

    # The other equations are about constraint I
    for i in range(n - 1):
        system_equations[i + 1, i] = 1
        system_equations[i + 1, i + 1] = 1
        system_equations[i + 1, -1] = distances[i]

    # check for solvability
    rank_a = np.linalg.matrix_rank(system_equations[:, 0:n])
    rank_ac = np.linalg.matrix_rank(system_equations)

    # System of linear equation has no solution (not full rank)
    if rank_a != rank_ac:
        return gear_1

    # The system of linear equations has indefinite solutions (not expected)
    if rank_ac < n:
        return gear_1

    # Solve the system of linear equations
    gears = np.linalg.inv(system_equations[:, 0:n]).dot(system_equations[:, n])

    # Check validity of the solution
    if is_no_solution(distances, gears):
        return gear_1

    # convert gear 1 to integer fraction
    frac_gear_1 = Fraction(gears[0]).limit_denominator()
    gear_1[0] = int(frac_gear_1.numerator)
    gear_1[1] = int(frac_gear_1.denominator)

    # return the solution
    return gear_1


# Check if the solution is valid
def is_no_solution(distances, gears):
    # Check constraint I
    for i in range(len(distances)):
        if gears[i] + gears[i + 1] - distances[i] > 0.01:
            return True

    # Check constraint II
    if gears[0] != gears[-1] * 2:
        return True

    # Check constraint III
    for gear in gears:
        if gear < 1:
            return True

    # no constraint has been violated thus the solution is valid
    return False

测试(test_pegs.py)

from unittest import TestCase

from pegs import solution


class Test(TestCase):
    def test_solution(self):
        # provided test-cases
        self.assertEqual([12, 1], solution([4, 30, 50]))
        self.assertEqual([-1, -1], solution([4, 17, 50]))

        # my own test-cases
        self.assertEqual([10, 3], solution([5, 10]))

        # test-cases from Stack Overflow @thiswind
        self.assertEqual([100, 3], solution([1, 51]))
        self.assertEqual([20, 1], solution([1, 31]))
        self.assertEqual([20, 1], solution([1, 31, 51, 71]))
        self.assertEqual([20, 1], solution([1, 31, 51, 71, 91]))
        self.assertEqual([4, 1], solution([4, 9, 17, 31, 40]))

答案 11 :(得分:-1)

我的python代码使用相当基本的操作来完成任务,而不会强行执行它。但是我很懒,没有真正评论,所以您必须自己弄清楚。它通过了foobar解决方案,因此肯定可以工作。

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