Как стать автором
Обновить

Комментарии 24

А кроме прямого перебора никакие оптимизированные методы не искали?
Первое что приходит в головы, найти самое большое число, и начать поиск соседей с него, но этот вариант отсеивается тем, что его соседи могут быть не большими. Сейчас рассматриваю
совет сохранять хэши перебранных ранее вариантов…
Ну можно еще посмотреть в сторону наибольших сумм с соседями, либо какого-нибудь анализа сумм строк/столбцов/даигоналей для поиска групп наибольших чисел.
+1. Тоже ждал какого-нибудь интересного алгоритма из динамического программирования.

Вызывает удивление как этот факт, так и выбранные структуры данных. Два new List и new Element на каждый чих (т.е. примерно 20*20*8^4 раз) вводят меня в ступор, честно говоря (надеюсь, компилятор их соптимизирует)

Искал оптимизацию полного перебора, хотя бы тупое отсечение по нехватке миллиона на умножение текущего элемента для превышения текущего максимума. Так как все числа меньше 100, если текущий максимум превышает 50 00 00 00, или 50 миллионов, пропускать элементы со значением 50 или ниже.
Ещё — ваш метод GetFactorCombinations не позволяет цепочки в форме ромба, т.е. перебор ещё и неполон. Цепочка в форме ромба — от стартового элемента вниз-вправо, вниз-влево, вверх-влево.

Так как все числа меньше 100, если текущий максимум превышает 50 00 00 00, или 50 миллионов, пропускать элементы со значением 50 или ниже.

И это действительно дельный совет. То есть, максимально возможный результат произведения это 100*100*100*100 = 100 000 000. Если комбинация со значением произведения уже равна 80 000 000 можно игнорировать все числа меньше 80. Возьму на вооружение
На счет цепочки в форме ромба. Только что проверил, их он тоже учитывает.

гляньте на число 78 нулевая строка, первый столбец.

Хмм, рекурсивный, кажется, полный, а вот итеративный — нет. Разве что у меня глазной дебаггер заглючил :)

Максимальное произведение — 96059601. У вас же максимальное число в ячейке 99, а не 100.
А когда ищете четвёртого соседа, то можно не всех найденных добавлять, а максимального из соседей, чтобы не делать лишние умножения потом, по всем найденным комбинациям.
когда ищете четвёртого соседа, то можно не всех найденных добавлять, а максимального из соседей

Согласен. Рациональная идея.
Если бы речь шла про сумму, можно было бы все распараллелить и оптимизировать через операцию свертки (Conv2D в данном случае) с несколькими ядрами. С другой стороны, так как числа по определению > 0, то логарифм произведения есть сумма логарифмов. Так что можно посчитать поэлементно логарифм, а потом применить операцию свертки.
PS — идея абсолютно не протестирована ).

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

Да, верно. Я думал про ядра W как 3D тензор размерности (4, 4, N) состоящий из нулей и единиц, где N — количество валидных цепочек в матрице 4x4. Тогда операция Conv2D(X, W) даст 3D тензор, в котором пространственные индексы максимального элемента укажут на под-матрицу (4,4) исходной матрицы, а третий индекс укажет на конкретное ядро, т.е. укажет какие 4 элемента надо выбрать.

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

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

Имхо медиана значений матрицы будет неплохим кандидатом.

Если оптимизировать для длины 4, то можно было бы вычислить в отдельный массив результат умножения пары чисел (4 варианта для каждой ячейки — вправо, вниз, вниз-влево, вниз-вправо), и затем перебирать уже пары для этого нового массива. Причем перебирать не все пары "пар", а только те, которые дают цепочку, подходящую под задание (скажем, "вниз" от текущей ячейки умножить на "вправо" от ячейки вниз-вправо), и без дублирования "фигур". Есть необоснованное подозрение, что в таком случае получим оптимальное число операций умножения. Кроме того, необязательно вычислять весь массив пар сразу — достаточно вычислить 3 строки массива для 4 строк исходных данных, поискать фигуры в них, и потом для каждой строки данных отбрасывать верхнюю строку массива и вычислять одну новую.

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

UPD: я подумал еще раз и понял, что сильно быстрее не станет, извиняюсь.
Комментария для автора: А можно более реальные алгоритмы разбирать? Например выборку-формулы для trend \ hot допустим видео, как делают такое на разных сайтах.
Спасибо, интересная задача!
Я применил некоторые оптимизации, результат ускоряется очень сильно. Понятно, что 20x20 будет работать быстро и без опnимизаций, поэтому я мерил производительность на двух случаях: 1) матрица 10,000x10,000, заполненная случайными значениями, и 2) матрица 1,000x1,000, заполненная «наихудшими» значениями так, чтобы максимальное произведение четырех соседних элементов было в самом конце.
Без оптимизаций первый вариант я не дождался, когда закончит вычисления, а второй вариант — 30 секунд.
Оптимизация, которую нашел vesper-bot, — пропускать числа, умножение которых на 99*99*99 будет меньше уже найденного результата, улучшает сильнее всего — «случайный» вариант ускоряется до 8 секунд, наихудший — никак не ускоряется (так как я специально подбирал для него значения!)
Кроме этого, если текущее значение = 99*99*99*99, то можно дальше не искать, так как это максимально возможный результат. Это ускоряет случайный вариант до 6 секунд.
Далее, так как перебирать всех соседей всех клеток на каждом шаге дорого, то до начала работы можно вычислить уникальные пути из четырех клеток. Их оказалось всего 102. Это ускоряет случайный вариант до 3 секунд, а наихудший — с 30 до 2 секунд.
При этом в случайном варианте заполнение матрицы случайными значениями занимает больше времени, чем решение задачи. Поэтому последня оптимизация — не создавать всю матрицу с нуля, а создать первые 4 строчки, а потом сдвигать их вверх, вычисляя четвертую строчку на каждом шаге. Также, на данном этапе проверка выхода за границу матрицы уже дает ощутимый вклад, поэтому еще одна оптимизация — расширить матрицу на три клетки во все стороны, заполненные нулями. Это ускоряет случайный вариант до 0.2 секунд, а наихудший — до 1 секунды, больше я ускорить не смог. Еще я проверил идею заменить произведение суммой логарифмов, но особо результат не ускорился (но и не замедлился).

Интересно, что хотя сложность задачи совершенно явно O(n*n), но для случайной матрицы, если она достаточно большая, рано или поздно найдется максимальное произведение (99*99*99*99), и дальше можно будет не искать. Учитывая, что оптимизированная версия создает матрицу построчно, скорость работы в среднем случае для случайной матрицы, похоже, будет O(n). Именно поэтому «случайный» вариант работает в 10 раз быстрее на большей матрице.
А если число в ячейке не ограничивать диапазоном 0-99?
А если разрешить отрицательные значения?
В случае если разрешить отрицательные значения, придется игнорировать комбинации с нечетным количеством отрицательных чисел
В общем случае не придется, ведь у нас может быть задан нечетный размер комбинации, а таблица при этом может сгенериться полностью из отрицательных чисел.
я бы построил граф с весами и нашел Гамильтонову цепь глубиной 4 (в Вашем случае). Поскольку у Вас достаточно жесткая структура(матрица), то так же можно легко построить список деревьев (те самые «уникальные пути») и пройтись по ним, найдя дерево с самым большим весом. НЕ используйте массивы для таких задач
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории