Решение рекурсивной последовательности

В последнее время я ради развлечения решал некоторые задачи из Google Foobar, и вот уже более 4 дней застрял в одной из них. Речь идет о рекурсивной функции, определенной следующим образом:

R(0) = 1
R(1) = 1
R(2) = 2
R(2n) = R(n) + R(n + 1) + n (for n > 1)
R(2n + 1) = R(n - 1) + R(n) + 1 (for n >= 1)

Задача состоит в том, чтобы написать функцию answer(str_S), где str_S — это строковое представление числа S с основанием 10, которое возвращает наибольшее n такое, что R(n) = S. Если такого n нет, вернуть "None". Кроме того, S будет положительным целым числом, не превышающим 10^25.

Я много исследовал рекурсивные функции и решение рекуррентных отношений, но безуспешно. Я вывел первые 500 чисел и не нашел никакой связи с каждым из них. Я использовал следующий код, который использует рекурсию, поэтому он становится очень медленным, когда числа становятся большими.

def getNumberOfZombits(time):
    if time == 0 or time == 1:
        return 1

    elif time == 2:
        return 2

    else:
        if time % 2 == 0:
            newTime = time/2
            return getNumberOfZombits(newTime) + getNumberOfZombits(newTime+1) + newTime

        else:
            newTime = time/2 # integer, so rounds down
            return getNumberOfZombits(newTime-1) + getNumberOfZombits(newTime) + 1

Задача также включала несколько тестовых случаев, вот они:

Test cases
==========

Inputs:
    (string) str_S = "7"
Output:
    (string) "4"

Inputs:
    (string) str_S = "100"
Output:
    (string) "None"

Я не знаю, нужно ли мне решать рекуррентное отношение к чему-то более простому, но так как есть одно для четных и одно для нечетных чисел, мне это очень трудно сделать (я еще не изучал это в школе, поэтому все, что я знаю об этом предмете, из статей в Интернете).

Итак, любая помощь, которая поможет мне закончить эту задачу, будет приветствоваться :)


person Gonçalo Santos    schedule 04.12.2014    source источник
comment
Я думаю, вы должны знать продвинутую математику и использовать быстрое возведение матриц в степень.   -  person Piotr Dabkowski    schedule 04.12.2014
comment
Это может помочь: stackoverflow.com/questions/1988804/   -  person Lambda Fairy    schedule 05.12.2014
comment
@LambdaFairy, может быть, это оно! Я думал, что это не будет иметь большого значения, но оказалось, что это НАМНОГО, намного быстрее, чем раньше! Спасибо! Я попытаюсь реализовать это сейчас, я скажу вам, помогло ли это: D   -  person Gonçalo Santos    schedule 05.12.2014
comment
@LambdaFairy, да, это сработало :) Я пробовал это раньше, но, должно быть, я сделал какую-то ошибку при кодировании, и скорость оказалась примерно такой же. Кроме того, я не знал, что это называется Memoization. Что ж, с некоторыми другими оптимизациями я заставил его работать и нашел ответ менее чем за секунду! Спасибо!   -  person Gonçalo Santos    schedule 05.12.2014
comment
@GonçaloSantos Добавьте свой ответ и примите его. Просветит всех нас :)   -  person Bhargav Rao    schedule 02.01.2015
comment
@BhargavRao О, хорошо, я обязательно это сделаю! Я немного занят в данный момент, но я сделаю это, как только смогу :)   -  person Gonçalo Santos    schedule 03.01.2015


Ответы (2)


Вместо того, чтобы пытаться математически упростить эту функцию, я упростил алгоритм в Python. Как предложил @LambdaFairy, я реализовал запоминание в функции getNumberOfZombits(time). Эта оптимизация намного ускорила работу функции.

Затем я перешел к следующему шагу, пытаясь увидеть, что было входом для этого количества кроликов. Я анализировал функцию раньше, наблюдая за ее графиком, и я знал, что четные числа сначала получают более высокие выходные значения, и только через некоторое время нечетные числа достигают того же уровня. Поскольку нам нужен самый высокий вход для этого вывода, мне сначала нужно было искать в четных числах, а затем в нечетных числах.

Как видите, нечетным числам всегда требуется больше времени, чем четным, чтобы достичь того же результата. График функции

Проблема в том, что мы не могли искать числа, увеличивающиеся каждый раз на 1 (это было слишком медленно). Чтобы решить эту проблему, я реализовал алгоритм, похожий на двоичный поиск. Во-первых, я искал четные числа (с помощью алгоритма, подобного бинарному поиску), пока не нашел один ответ или у меня не осталось чисел для поиска. Затем я сделал то же самое с нечетными числами (опять же, с алгоритмом бинарного поиска), и если ответ был найден, я заменил им все, что у меня было раньше (поскольку он обязательно был больше, чем предыдущий ответ).

У меня есть исходный код, который я использовал для решения этой проблемы, поэтому, если он кому-то нужен, я не против поделиться им :)

person Gonçalo Santos    schedule 03.01.2015

Ключом к решению этой головоломки было использование бинарного поиска.

Как вы можете видеть из генераторов последовательностей, они полагаются примерно на n/2 рекурсии, поэтому вычисление R(N) занимает около 2*log2(N) рекурсивных вызовов; и, конечно, вам нужно сделать это как для нечетных, так и для четных.

Это не так уж плохо, но вам нужно выяснить, где искать N, который даст вам ввод. Для этого я сначала реализовал поиск верхней и нижней границ для N. Я поднял N по степени двойки, пока не получил N и 2N, формирующие нижнюю и верхнюю границы соответственно для каждой последовательности (нечетной и четной).

С этими границами я мог бы затем выполнить бинарный поиск между ними, чтобы быстро найти значение N или его отсутствие.

person Sean    schedule 04.10.2015