Оптимизация решения задачи python

Рейтинг: -1Ответов: 2Опубликовано: 26.02.2023

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

Условие задачи:

В строю отважной армии Сарумана друг за другом стоят n n орков, рост i i-го из них равен a i a i ​ условных единиц. Новобранец-орк Гримморхус тоже собирается встать в этот строй, причём Саруману хочется поставить Гримморхуса на такую позицию p p, чтобы f ( p ) f(p) = [количество орков левее Гримморхуса того же роста, что и Гримморхус] умножить на [количество орков правее Гримморхуса с ростом, не равным росту Гримморхуса] было максимально. Для этого Гримморхус может встать в начало строя, в её конец, или между любыми двумя соседними орками. К сожалению ни Гримморхус, ни Саруман не могут точно вспомнить рост Гримморхуса, у них есть только m m предположений о том, каким он может быть, и для каждого из них они хотели бы знать оптимальную позицию, на которую Гримморхусу стоило бы встать.

Описание входных данных

В первой строке даны n n целых чисел a i a i ​

  • рост орков, стоящих в строю (1 ⩽ ⩽ n , a i n,a i ​ ⩽ ⩽ 1 0 5 10 5 ). Во второй строке даны m m целых чисел x i x i ​
  • предполагаемый рост Гримморхуса (1 ⩽ ⩽ m , x i m,x i ​ ⩽ ⩽ 1 0 5 10 5 ).

Описание выходных данных

В единственной строке выведите выведите m целых чисел - значение f ( p ) f(p) в оптимальной для данного роста позиции.

Формат ввода

1 1 2 2 2 2 1 1 1 1 2 1

1 2 1

Формат вывода

8 12 8

Моё решение:

row = input().split(' ')
heights = input().split(' ')
result = []


def right_sum(array, index, height):
    rsum = 0
    for i in range(index, len(array)):
        if array[i] != height:
            rsum += 1
    return rsum


def left_sum(array, index, height):
    lsum = 0
    for i in range(0, index):
        if array[i] == height:
            lsum += 1
    return lsum


for height in heights:
    maxCount = 0
    for i in range(0, len(row)):
        current = left_sum(row, i, height) * right_sum(row, i, height)
        if current > maxCount:
            maxCount = current
    result.append(str(maxCount))

print(' '.join(result))

Ответы

▲ 3Принят

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

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

А для быстрого расчета целевой функции нужно знать количество текущих значений слева - это накапливается в словаре currcnt, и количество отличных от текущего значений справа - это легко найти, зная длину куска справа, и разность количества текущего значения во всем списке (overcnt) и слева. Предобработка линейная, обеспечивается O(1) для каждого вычисления целевой функции.

Итого получается линейный алгоритм, работает не более 0.15 с при n,m<=100000

import time, collections, random

n = 10
a = [random.randint(1, 5) for _ in range(n)]
m = [random.randint(1, 5) for _ in range(n)]
print(a)
print(m)
print()

def opt(a, m):
    mset = set(m)
    n = len(a)
    overcnt = collections.Counter()
    currcnt = collections.Counter()
    for x in a:
        overcnt[x] += 1
    maxes = {x:0 for x in mset}
    for i, x in enumerate(a):
        currcnt[x] += 1
        if x in mset:
            t = currcnt[x] * ((n - i - 1) -  (overcnt[x] - currcnt[x]))
            maxes[x] = max(maxes[x], t)
    lst = [str(maxes[x]) for x in m]
    return(' '.join(lst))

print(opt(a, m))

[2, 4, 2, 4, 5, 1, 5, 2, 5, 1]
[3, 2, 1, 5, 3, 5, 5, 3, 5, 5]

0 12 3 4 0 4 4 0 4 4
▲ 1

Представляю Вам одно из возможных решений по оптимизации Вашего алгоритма:

import numpy as np
import time

row = np.array(input().split())
heights = np.array(input().split())
result = np.zeros(len(heights), dtype=int)

def right_sum(array, index, height):
    return len(array[index:][array[index:] != height])

def left_sum(array, index, height):
    return len(array[:index][array[:index] == height])

for i, height in enumerate(heights):
    maxCount = 0
    for j in range(len(row)):
        current = left_sum(row, j, height) * right_sum(row, j, height)
        if current > maxCount:
            maxCount = current
    result[i] = maxCount

print(' '.join(result.astype(str)))

Предложенный оптимизированный код использует библиотеку NumPy для создания массивов, которые работают быстрее и используют меньше памяти, чем обычные списки в Python.

Было использовано булевую индексацию, чтобы найти элементы, которые не равны или равны определенному значению. Позже использовалась функция len() для подсчета количества таких элементов.

Далее были задействованы генераторы списков для оптимизации кода. Вместо того, чтобы создавать отдельный список для результата, формируется массив нулей, который в последствии заполняется значениями, используя цикл и функцию enumerate(), которая позволяет перебирать значения массива вместе с их индексами.

Также была изменена логика цикла. Вместо того, чтобы проходить по каждому элементу списка row для каждого элемента списка heights, мы проходим по каждому элементу списка row только один раз и вычисляем максимальное значение для каждого элемента списка heights.

Метод astype() добавлен для преобразования элементов массива в строковые значения.

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

Результат Вашего алгоритма:

введите сюда описание изображения

Результат оптимизированого алгоритма:

введите сюда описание изображения

По просьбе автора доработал алгоритм, без использования Numpy. Вышло с минимальным, но действенным вмешательством в оригинальный алгоритм. Этот код использует словарь heights_dict для сохранения значений maxCount для каждой высоты, поэтому выполнение кода становится быстрее. Как результат новый оптимизированный алгоритм исполняется более чем в 135 раз быстрее, чем исходный. И в 38 раз быстрее, чем первый вариант оптимизации.

Новый адаптированный алгоритм без использования NumPy:

import time

row = input().split(' ')
heights = input().split(' ')
result = []

def right_sum(array, index, height):
    rsum = 0
    for i in range(index, len(array)):
        if array[i] != height:
            rsum += 1
    return rsum

def left_sum(array, index, height):
    lsum = 0
    for i in range(0, index):
        if array[i] == height:
            lsum += 1
    return lsum

heights_dict = {}
for height in heights:
    if height in heights_dict:
        result.append(str(heights_dict[height]))
    else:
        maxCount = 0
        for i in range(0, len(row)):
            current = left_sum(row, i, height) * right_sum(row, i, height)
            if current > maxCount:
                maxCount = current
        heights_dict[height] = maxCount
        result.append(str(maxCount))

print(' '.join(result))

Результат оптимизированого алгоритма без NumPy:

введите сюда описание изображения

Есть еще один вариант, это последняя модификация, где убраны повторяющиеся вычисления длин с помощью методов массива array[index:] и array[:index], которые позволяют находить подмассивы с помощью срезов и тем самым еще больше ускоряют выполнение данного кода.

Код последней модификации с срезами подмассивов:

import time

row = input().split(' ')
heights = input().split(' ')
result = []

def right_sum(array, index, height):
    rsum = 0
    for h in array[index:]:
        if h != height:
            rsum += 1
    return rsum

def left_sum(array, index, height):
    lsum = 0
    for h in array[:index]:
        if h == height:
            lsum += 1
    return lsum

heights_dict = {}
for height in heights:
    if height in heights_dict:
        result.append(str(heights_dict[height]))
    else:
        maxCount = 0
        for i in range(0, len(row)):
            current = left_sum(row, i, height) * right_sum(row, i, height)
            if current > maxCount:
                maxCount = current
        heights_dict[height] = maxCount
        result.append(str(maxCount))

print(' '.join(result))

Результат последней модификации с срезами подмассивов:

введите сюда описание изображения