Как уменьшить временную сложность программы, которая зацикливается огромное количество раз? - PullRequest
0 голосов
/ 08 октября 2019

Я пытаюсь закодировать систему генерации чисел (целый день), она начинается с 3, и значение уменьшается на 1, пока не достигнет 1. Как только оно достигнет 1, оно сбрасывает свой номер к новому начальному значению, 2x какпервый номер. И затем с каждым новым сбросом 2x в качестве первого числа предыдущего сброса. Как показано ниже:

3 2 1 6 5 4 3 2 1 12 11 10 9 8 7 6 5 4 3 2 1 ...

Программа должна вернуть значение n-й позиции, которую запросил пользователь.

пользователь может ввести n <(10 ^ 12) </em>

Попытка пропустить некоторые шаги, если целевая позиция находится далеко от текущей позиции цикла

# requesting position
tt = int(input())
# starting t 
t = 1
# starting value
sv = 3
# current value
cv = 3

while (True):

    # check if we have reached target time
    if (t == tt):
        # print the current value
        print("{}".format(cv))
        break

    # if there's a loong way to go, skip some 
    if (t + cv < tt):  # starting_t+current_value<target_t
        t += cv  
        sv = sv * 2  
        cv = sv  

        if cv % 2 == 0:
            if (t + cv // 2 < tt):
                t += cv // 2
                cv = cv // 2
                continue
        continue

    # check if value is 1 and double it
    if (cv == 1):
        # set new starting value
        sv = sv * 2
        # set new current value
        cv = sv
        # elapse time
        t += 1
        continue

    # elapsed time
    t += 1
    # changed value
    cv -= 1

В настоящее время этой программе требуется более 2 минут, чтобы вернуть результат для n>10 ^ 10 . Мне нужно максимально сократить время, затрачиваемое на процесс. Что я могу сделать, чтобы сократить время, затрачиваемое на процесс? (ожидайте сократить его до пары секунд). Любая ссылка может быть полезной

Ответы [ 4 ]

1 голос
/ 08 октября 2019

Вы можете вычислить свой ответ без всякой петли:

После сброса n ваш номер будет 3*2^n. Таким образом, вы сделали 3 * (2 ^ {n} -1) шагов.

Итак, если пользователь вводит число x, вам нужно найти, сколько сбросов вы уже сделали (чтоцелочисленное значение log_2(x/3)), давайте назовем R этот номер и выясним, сколько еще шагов вы выполнили с момента этого сброса, давайте назовем этот номер S.

Тогда ваше решение будет 3*(2^{R}-1) - S.

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

0 голосов
/ 08 октября 2019

Проблема может быть решена аналитически без перебора чисел и вычислений, как указано выше. Наконец-то удалось решить проблему (конечно, с помощью)

python
import math
t = int(input())   #input the position
power_a = (t/3)+1
a = math.log(power_a, 2)

if(a%1 == 0):
    n = int(a-1)
else:
    n = int(a)

prev_step = 3*(2**n-1)  #how many steps already passed
step = int(3*2**n)   #begining of current step

steps_needed = int(t-prev_step)  #steps need to pass
count = 1

print (step-(steps_needed-1))
0 голосов
/ 08 октября 2019

Первый шаг состоит в том, чтобы заметить, что ваша основная последовательность «пиков» представляет собой геометрическую прогрессию :

x_k = a * r ^ k

Сумма первых n членов геометрической прогрессии геометрический ряд :

sum(x_k for k in 1 to n) = a * (1 - r ^ n) / (1 - r)

Целевая последовательность в основном получается путем вычитания индекса из наибольшего члена ряда, не превышающего сам индекс.

В кодеэто выглядит следующим образом:

# note that this uses integer division hence expects integer `r`
def geom_series_int(a, r, n):
    return a * (1 - r ** n) // (1 - r)


def my_seq_int(n, a=3, r=2): 
    i = 1
    cumsum = geom_series_int(a, r, i) 
    while cumsum < n + 1:
        i += 1
        cumsum = geom_series_int(a, r, i)
    return cumsum - n


print([my_seq_int(i) for i in range(25)])
# [3, 2, 1, 6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 24, 23, 22, 21]

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

def i_geom_progression(a, r): 
    i = 0 
    while True: 
        yield a * r ** i 
        i += 1


def i_geom_series(a, r):
    gp = i_geom_progression(a, r)
    result = next(gp)
    while True:
        yield result
        result += next(gp)


def my_seq(n, a=3, r=2): 
    gs = i_geom_series(a, r) 
    cumsum = next(gs) 
    while cumsum < n + 1:
        cumsum = next(gs)
    return cumsum - n


print([my_seq(i) for i in range(25)])
# [3, 2, 1, 6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 24, 23, 22, 21]

Сложность вычислений в обоих случаях составляет log(n).


Более эффективный подход состоит в том, чтобы сделать это аналитически, решив для k выражение для геометрического ряда и используя индекс n в качестве прокси для кумулятивной суммы:

n = a * (1 - r ^ k) / (1 - r)

становится:

k = log_r(1 - n * (1 - r) / a)

и, принимая неотъемлемую часть, становится:

import math


def my_seq_analytic(n, a=3, r=2):
    k = int(math.log2(1 - n * (1 - r) // a) / math.log2(r)) + 1
    return geom_series_int(a, r, k) - n


print([my_seq_analytic(i) for i in range(25)])
# [3, 2, 1, 6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 24, 23, 22, 21]

Это самый быстрый подход.


В целом, предлагаемые методы намного быстрее, чем первоначально предложенный метод, упрощение которого описано в my_seq_loop() ниже:

def my_seq_loop(n, a=3, r=2):
    peak = a
    for i in range(1, n + 1):
        if a == 1:
            peak *= r
            a = peak
        else:
            a -= 1
    return a


print([my_seq_loop(i) for i in range(25)])
# [3, 2, 1, 6, 5, 4, 3, 2, 1, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 24, 23, 22, 21]

. Дают некоторое представление осроки, см. тесты ниже:

%timeit my_seq_loop(10 ** 8)
# 1 loop, best of 3: 6.65 s per loop
%timeit my_seq(10 ** 8)
# 100000 loops, best of 3: 14.1 µs per loop
%timeit my_seq_int(10 ** 8)
# 100000 loops, best of 3: 11.7 µs per loop
%timeit my_seq_analytic(10 ** 8)
# 1000000 loops, best of 3: 938 ns per loop
0 голосов
/ 08 октября 2019

Просто вы можете сделать:

def get_n_th_number(x, n):
    if n <= x:
        return x - n + 1

    return get_n_th_number(2 * x, n - x)

x = 3

assert get_n_th_number(x, 1) == 3
assert get_n_th_number(x, 3) == 1
assert get_n_th_number(x, 4) == 6
assert get_n_th_number(x, 9) == 1
assert get_n_th_number(x, 10) == 12

Сложность времени: log2(n / x)

Сложность пространства: log2(n / x) (из-за рекурсии.)

...