Какое число остается после многократного устранения идеальных квадратов? - PullRequest
17 голосов
/ 18 января 2012

Я практиковал проблемы SRM в Topcoder.Я столкнулся с этой проблемой

Постановка задачи: Сегодня канун Рождества.Люди во всем мире отмечают этот праздник.Следующая история происходит в стране оленей, где живет Санта-Клаус.

Конфеты любят олени.У них есть n конфет.Конфеты пронумерованы от 1 до n.Дашер - один из оленей.Он хочет съесть одну из конфет.Чтобы выбрать тот, который он съест, Дашер использует следующий метод: пока есть более одного кусочка конфеты: отбросьте все конфеты, которые пронумерованы идеальными квадратами (то есть конфеты 1, 4, 9, 16, 25 и т. Д.),Перемаркируйте оставшиеся k конфет с 1 по k, сохраняя номера в том же порядке.Как только остался только один кусочек конфеты, Дашер его съест.

Вам дано инт.Ваш метод должен вычислить и вернуть число, изначально присвоенное кусочку конфеты, съеденному Дашером.

Я решил проблему с помощью ArrayList, но мое решение не удалось для очень больших чисел (исключение Java Heap Sapce).Я думал, можно ли решить проблему в O (1) сложности пространства.

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

Я повторно открыл этот вопрос с формулировкой проблемы , так что маэстро в Stackoverflow могут помочь мнечтобы решить эту проблему в O (1) Пространство сложности

Ответы [ 6 ]

12 голосов
/ 18 января 2012

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

Давайте проследим пример этой проблемы, где n = 10. Затем мы получим следующее:

1  2  3  4  5  6  7  8  9  10
X        X              X

2  3  5  6  7  8  10
X        X

3  5  7  8  10
X        X

5  7  10
X

7  10
X

10

Теперь предположим, что мы хотим вычислить конечный результат для этой задачи.Мы знаем, что когда мы закончим, съеденная конфета находится на позиции 1, так как остался только один кусочек конфеты.Итак, давайте попробуем настроить его так:

1

Теперь мы знаем, что на предыдущей итерации конфетка с индексом 1 должна была съесть.Это означает, что последний леденец фактически находился в позиции 2 в прошлый раз:

?   2

На итерации до этого мы знаем, что поскольку конфета 1 была съедена, наша конфета должна была фактически находиться в позиции 3:

?   ?   3

На этом этапе мы снова вспоминаем одну итерацию.Мы знаем, что конфеты 1 были съедены, но конфеты 4 тоже были съедены.Это означает, что индекс нашей конфеты должен был равняться 5 на предыдущей итерации, поскольку, когда мы переводим ее в правильное положение, она должна пропустить одну позицию для 1-го элемента и одну позицию для 4-го элемента:

?   ?   ?   ?   5

Повторяя эту же логику, мы получаем, что предыдущий индекс был бы 7:

?   ?   ?   ?   ?   ?   7

Теперь, для следующего шага, мы знаем, что мы бы опустили конфету на две позиции, потому чтомы выпали 1-й и 4-й элементы.Однако, это поставило бы нашу конфету в положение 9, которое было бы удалено.Это означает, что вместо этого мы поставили бы конфету в положение 10:

?   ?   ?   ?   ?   ?   ?   ?   ?   10

На данный момент, поскольку осталось 10 конфет, мы знаем, что полностью изменили процесс и сделали это.Поскольку последним местом отдыха нашей конфетки была позиция 10, мы знаем, что ответом является то, что десятая конфета - это та, которая была съедена, что идеально соответствует нашей предыдущей работе!

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

  • По какому индексу в данный момент находится последняя конфета? Нам нужно это, чтобы знать, кудастоп.
  • Сколько квадратов ниже этого индекса? Нам нужно это знать, сколько элементов удаляется на каждом шаге.
  • Что будет дальшесовершенный квадрат? Нам нужно это знать, когда число квадратов, удаляемых каждый раз, увеличивается.
  • Какой последний индекс мы исследовали? Этот алгоритм работает, выполняя процесс в обратном направлении.Итак, в какой-то момент мы поймем, что запустили его один раз слишком много.Когда это происходит, нам нужно иметь возможность «сделать резервную копию» шага, чтобы перейти к последнему индексу, который не перескочил.

Учитывая это, мы имеем следующий алгоритм:

  • Установите текущий индекс равным 1.
  • Установите число меньших совершенных квадратов равным 1.
  • Установите следующий идеальный квадрат равным 4.
  • Установитепоследний меньший индекс равен 1.
  • Хотя текущий индекс меньше n:
    • Установите последний меньший индекс как текущий индекс (запомните решение до сих пор).
    • Установить текущий индекс + = количество меньших совершенных квадратов (запустить процесс в обратном направлении на один шаг)
    • Если текущий индекс равен следующему идеальному квадрату, добавить к нему один (крайний случай выполненияназад, если мы попали в идеальный квадрат, мы должны быть на шаг впереди него)
    • Если текущий индекс больше, чем следующий идеальный квадрат (теперь на каждом шаге удаляется больше чисел):
      • Установите идеальный квадрат, чтобы быть рядом с идеальнымquare.
      • Добавьте единицу к числу идеальных квадратов, меньших, чем индекс.
  • Возвращает последний меньший индекс.

Для хранения всех значений требуется только O (1) память.

Давайте попробуем пример! При n = 20, если мы проходим формальный процесс, мы получаем это:

1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
X        X              X                    X 

2  3  5  6  7  8 10 11 12 13 14 15 17 18 19 20
X        X              X                    X 

3  5  7  8  10 11 13 14 15 17 18 19
X        X              X

5  7 10 11 13 14 17 18 19
X        X              X

7 10 13 14 17 18
X        X

10 13 17 18
X        X

13 17
X

17

Если мы запустим наш алгоритм, мы получим

 Current Index       Next Square      Smaller Squares
 1                   4                1
 2                   4                1
 3                   4                1
 5                   9                2
 7                   9                2
 10                  16               3
 13                  16               3
 17                  25               4
 21                  25               4

Поскольку 21> 20, последний меньший индекс равен 17, поэтому мы возвращаем 17, что является правильным ответом!

Записано в виде кода на C, при условии, что нет целочисленного переполнения:

int EatenCandyIndex(int n) {
    int currIndex = 1;
    int numSmallerSquares = 1;

    /* Rather than tracking the next square, track the root of the next
     * square.  We can just square this to get the next square.
     */
    int rootOfNextSquare = 2;

    /* The last spot where the candy would be before we had Too Much Candy. */
    int lastIndex = 1;

    while (currIndex <= n) {
        lastIndex = currIndex;

        currIndex += numSmallerSquares;
        if (currIndex == rootOfNextSquare * rootOfNextSquare)
            ++currIndex;

        if (currIndex > rootOfNextSquare * rootOfNextSquare) {
            ++numSmallerSquares;
            ++rootOfNextSquare;
        }
    }

    return lastIndex;
}

Однако, как написано, этот алгоритм не особенно эффективен. В частности, посмотрите на его поведение в примере, где n = 20. Обратите внимание, что у нас есть три раунда, где размер шага равен одному, два с размером шага два и три и т. Д. Вместо того, чтобы эти раунды происходили явно, мы могли бы вместо этого вычислить сколько раундов должно произойти с таким размером шага, затем просто запустите все эти шаги одновременно. Таким образом, у нас всегда есть один раунд с размером один, один раунд с размером два, один раунд с размером три и т. Д. Чтобы сделать это, на каждом шаге нам нужно будет увидеть, какова наша следующая цель; это будет либо число n, либо следующий идеальный квадрат. Как только мы нашли нашу цель, нам нужно увидеть, сколько шагов требуется, чтобы туда добраться. Если текущий индекс равен i, а наша цель равна t, а размер шага равен k, то нам нужно принять & lceil; (t - i) / k & rceil; шаги, чтобы добраться туда. Используя симпатичный трюк с целочисленным делением, мы можем вычислить это как

int numSteps = ((t - i) + (k - 1)) / k;

Это дает нам следующий обновленный алгоритм:

int EatenCandyIndexFaster(int n) {
    int currIndex = 1;
    int numSmallerSquares = 1;

    /* Rather than tracking the next square, track the root of the next
     * square.  We can just square this to get the next square.
     */
    int rootOfNextSquare = 2;

    while (true) {
        /* Figure out what our target is. */
        int target = min(n, rootOfNextSquare * rootOfNextSquare);

        /* See how many steps are required. */
        int numSteps = ((target - currIndex) + (numSmallerSquares - 1)) / numSmallerSquares;

        /* See where we'd end up if we took one fewer than this many steps forward. */
        int lastIndex = currIndex + (numSteps - 1) * numSmallerSquares;

        /* Take that many steps forward. */
        currIndex += numSmallerSquares * numSteps;

        /* There is an edge case here: if we hit our number but it's a perfect square,
         * we want to return the previous value.
         */
        if (currIndex == n && n == rootOfNextSquare * rootOfNextSquare)
            return lastIndex;

        /* Otherwise, if we hit the target number exactly, return it. */
        if (currIndex == n)
            return currIndex;

        /* Otherwise, if we overshot the target number, hand back where we'd be if we
         * took one fewer step.
         */
        if (currIndex > n)
            return lastIndex;

        /* Oh well; didn't make it.  If we hit a perfect square, skip it. */
        if (currIndex == rootOfNextSquare * rootOfNextSquare)
            ++currIndex;

        ++numSmallerSquares;
        ++rootOfNextSquare;
    }
}

Эта оптимизированная версия алгоритма выполняется за время O (& radic; N) и использует пространство O (1). Причиной ограничения по времени является то, что каждый шаг алгоритма перемещается к следующему идеальному квадрату, и есть только O (& radic; N) совершенных квадратов меньше, чем N.

Надеюсь, это поможет!

9 голосов
/ 18 января 2012

Другой вариант того же самого:

a = floor(sqrt(N-1))
b = min((N-1)/a, a+1)
solution = a*b+1

Или, иначе говоря,

unsigned long long eats(unsigned long long N) {
    unsigned long long r = (unsigned long long)sqrt(N-1);
    while(r*r >= N) --r;
    while(r*(r+2) < N) ++r;
    if (N <= r*(r+1)) {
        return r*r+1;
    }
    return r*(r+1)+1;
}

Доказательство следует из анализа функции next, которая дает следующую позицию любой конфетыnext(n*n) = 0, так что это не частичная функция.Если a*a < N < (a+1)*(a+1), мы имеем next(N) = N - a.Таким образом, число формы n = a*(a+1) + 1 перемещается

a*(a+1)+1 -> a*a + 1 -> (a-1)*a + 1 -> ... -> 2*3 + 1 ->2*2 + 1 -> 1*2 + 1 -> 1*1 + 1 -> 0*1 + 1

Мы видим, что также числа формы a*a +1 достигают 1. Числа любой другой формы достигают квадрата, большего 1 в некоторой точке:

a*(a+1) -> a*a -> eliminated
a*(a+1) + r -> a*a + r -> (a-1)*a + r

для 2 <= r <= a.Если r = a, (a-1)*a + r = a*a является квадратом, что приводит к немедленному устранению.Если r < a, число, достигнутое после двух шагов, имеет одинаковую форму с одинаковым r.Продолжая, отсюда следует, что число достигает

(r+1)*(r+2) + r -> (r+1)*(r+1) + r -> r*(r+1) + r -> r*r + r -> r*r -> elimination.

Итак, мы видели

  • Число достигает точки 1 в процессе, если и только если оно имеет форму n*n + 1 или n*(n+1) + 1

Последнее число, достигшее первого места, начиная с конфет N, является, конечно, наибольшим числом этой формы, не превышающим N.Что и требовалось доказать.

5 голосов
/ 18 января 2012

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

from math import floor, sqrt, ceil

def is_square(i):
    sq = int(i**0.5)
    return i == sq*sq

def brute(n):
    seq = range(1, n+1)
    while len(seq) > 1:
        seq = [x for i,x in enumerate(seq, 1) if not is_square(i)]
    return seq[0]

def dasher(n):
    w = lambda i: floor(sqrt(4*i+1))-1
    q = lambda i: ceil((i**2+3)/4)
    return q(w(n-1)+1)

И проверить:

>>> b = [brute(i) for i in range(1, 10**3)]
>>> d = [dasher(i) for i in range(1, 10**3)]
>>> b[:25]
[1, 2, 3, 3, 5, 5, 7, 7, 7, 10, 10, 10, 13, 13, 13, 13, 17, 17, 17, 17, 21, 21, 21, 21, 21]
>>> b == d
True
4 голосов
/ 18 января 2012

Я думаю, что я мог бы кое-что здесь.

f(n) = 1 if n = 1
f(n) = f(n-floor(sqrt(n))) + floor(sqrt(n)) if n is not a perfect square
f(n) = f(n-1) if n is a perfect square

Базовый случай ясен.Случай «идеального квадрата» исходит из наблюдения, что если n является идеальным квадратом, он будет исключен при удалении идеальных квадратов, поэтому решение для него равнозначно решению на единицу меньше, чем оно.Последнее вытекает из наблюдения, что после удаления идеальных квадратов floor (sqrt (n)) и перенумерации вы сместили некоторые числа влево (но в остальном ответы те же).Мы можем проверить это в первых нескольких случаях ...

 n Answer f(n)
 1      1    1
 2      2    f(2-1) + 1 = f(1) + 1 = 1 + 1 = 2
 3      3    f(3-1) + 1 = f(2) + 1 = 2 + 1 = 3
 4      3    f(4-1) = f(3) = 3
 5      5    f(5-2) + 2 = f(3) + 2 = 3 + 2 = 5

Доказательство того, что это правильно, должно быть простым продолжением, и пока я его не завершу, я оставлю это как упражнение.Вы должны проверить это для большого количества случаев и видеть, работает ли это;если это не сработает, я удалю его.

РЕДАКТИРОВАТЬ:

Я думаю, что тенденция, которую я замечаю, и причина, по которой это работает, заключается в том, что для не квадратного пответ никогда не может быть меньше наибольшего квадрата меньше n.Я думаю, что причина этого в том, что вы никогда не сможете удалить m ^ 2 + 1 до того, как удалите все, что меньше или равно m ^ 2.Учитывая это, рекуррентные соотношения выше почти тривиально верны.

0 голосов
/ 18 января 2012

Вы знаете, что последний номер будет удален специальным номером 1. Если вы сгенерируете все номера, удаленные на 1, у вас будет набор, содержащий специальный номер. Так что все, что вам нужно сделать, это сгенерировать эти числа.

Итак, давайте посмотрим, есть ли шаблон.

Пусть n будет 150

Числа удалены 1

r = [1, 2, 3, 5, 7, 10, 13, 17, 21, 26, 31, 37, 43, 50, 57, 65, 73, 82, 91, 101, 111, 122, 133]

Массив r [i + 1] -r [i]

[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11]

Числа удалены 2

r = [4, 6, 8, 11, 14, 18, 22, 27, 32, 38, 44, 51, 58, 66, 74, 83, 92, 102, 112, 123, 134, 146]

Массив r [i + 1] -r [i]

[2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12]

Числа удалены 9 Мы знаем, что числа, удаленные на 9, будут отличаться в первых 2 элементах на 3, а первый элемент равен 9. Исходя из этого, мы можем сгенерировать числа, которые будут удалены по этой схеме 3,3,4,4,5, 5 [9, 12, 15, 19, 23, 28, 33, 39, 45, 52, 59, 67, 75, 84, 93, 103, 113, 124, 135, 147] [3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12]

import math

def getSpecial(n):
    sp = list()
    s = 1
    while((s * s) <= n):
        sp.append(s*s)
        s += 1
    return sp

def bruteForce(n):
    nu = range(n+1)
    nu.pop(0)
    while(len(nu) > 1):
        sp = getSpecial(len(nu))
        removed = list()
        for x in sp[::-1]:
            removed.append(nu.pop(x-1))
    return nu[0]

def fancyMathWitchCraft(n):
    sp = getSpecial(n)    
    oneset = [1]
    j = 0.0
    while(oneset[-1] <= n):
        oneset.append( oneset[-1] + int(1 + 1 * math.floor(j/2)) )
        j = j + 1.0

    if(oneset[-1] <= n):
        return oneset[-1]
    if(oneset[-2] <= n):
        return oneset[-2]
    if(oneset[-3] <= n):
        return oneset[-3]



def main():
    for x in range(1,2000):
        if(bruteForce(x) != fancyMathWitchCraft(x)):
            print(x, bruteForce(x), fancyMathWitchCraft(x))
    print("Done")

if __name__ == "__main__":
    main()

Доказательством этого является, вероятно, тот факт, что последний совершенный квадрат удалит только 1 число, поэтому последнее число будет получено из наибольшего непрерывного сегмента, который не будет затронут после 1-й итерации, и это будет последний сегмент. Если вы ДЕЙСТВИТЕЛЬНО хотите получить математическое доказательство для этого, вам нужно ответить на этот вопрос в meta.stackoverflow

0 голосов
/ 18 января 2012
n=1, eats=1  
n=2, eats=2  
n=3, eats=3  
n=4, eats=3  
n=5, eats=5  
...  

Видите ли вы, как складывается картина? Придумайте формулу и докажите, что формула верна, используя математическую индукцию

Вот код C ++:

#include <iostream>
#include <cmath>

using namespace std;

bool is_perfect_square(int value)
{       
    return pow( static_cast<double>(static_cast<int>(sqrt(static_cast<double>(value)))), 2.0) == value;
}

int EatEm(int n)
{
    while (is_perfect_square(n))
    {
        n -= (static_cast<int>(sqrt(static_cast<double>(n)) - 1));
    }

    return n;
}

int main()
{           
    int res = EatEm(25);
    cout << res << endl;
    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...