история
Моя компания рассылает еженедельную рассылку всем сотрудникам компании. В эти новостные рассылки включена загадка, а также крик тому, кто в компании первым отправил электронное письмо / предоставил решение загадки на прошлой неделе. Большинство из этих загадок довольно тривиальны и, честно говоря, довольно скучны для технической компании, но один, несколько месяцев назад, привлек мое внимание.
Оригинальная загадка:
Учитывая форму ниже:
У вас есть натуральные числа от 1 до 16. Подберите их все так, чтобы все смежные ряды и смежные столбцы суммировались до 29.
Например, одним из таких решений этой головоломки (которое было «каноническим» решением, которое я отправил в информационный бюллетень) было следующее:
Однако в процессе ее решения я нашел довольно интересную информацию:
- Есть значительно больше решений, чем только это; На самом деле существует 9368 решений.
- Если развернуть набор правил, чтобы требовать, чтобы только строки и столбцы были равны друг другу, а не обязательно 29, вы получите 33 608 решений:
- 4440 решений на сумму 27.
- 7400 решений на сумму 28.
- 9 368 решений на сумму 29.
- 6 096 Решений на сумму 30.
- 5,104 решения на сумму 31.
- 1200 решений на сумму 32.
Поэтому я и мои коллеги (хотя в основном только мой менеджер, так как он был единственным человеком, кроме меня, обладающим навыками программирования «Общего назначения») поставили перед собой задачу, которая длилась большую часть месяца - у нас была другая, реальная работа - сопутствующие обязательства, которые мы должны были выполнить - попытаться написать программу, которая найдет каждое решение максимально быстрым способом.
Исходная статистика
Самая первая программа, которую я написал для решения проблемы, просто проверяла случайные решения снова и снова и останавливалась, когда находила решение. Если вы провели математический анализ этой проблемы, вы, вероятно, уже знаете, что это не должно сработать; но как-то мне повезло, и программе понадобилось всего минуту, чтобы найти единственное решение (то, которое я выложил выше). Повторные запуски программы часто занимали целых 10 или 20 минут, поэтому очевидно, что это не было строгим решением проблемы.
Я переключился на Рекурсивное решение, которое перебирало каждую возможную перестановку головоломки, и отбрасывал множество решений одновременно, удаляя суммы, которые не складывались. То есть, если бы первая строка / столбец, которую я сравнивал, уже не была равна, я мог бы немедленно прекратить проверку этой ветви, зная, что ничто другое, переставленное в головоломку, не изменит это.
Используя этот алгоритм, я получил первый «правильный» успех: программа могла сгенерировать и выплюнуть все 33 608 решений примерно за 5 минут.
У моего менеджера был другой подход: зная, основываясь на моей работе, что единственно возможные решения имели суммы 27, 28, 29, 30, 31 или 32, он написал многопоточное решение, которое проверяло возможные суммы только для этих конкретных значений. Ему удалось запустить свою программу всего за 2 минуты. Так что я повторил еще раз; Я хэшировал все возможные суммы в 3/4 цифры (в начале программы; она учитывается в общем времени выполнения) и использовал «частичную сумму» строки, чтобы найти оставшееся значение на основе ранее заполненной строки, а не протестировал все оставшиеся значения и сократил время до 72 секунд. Затем, используя некоторую многопоточную логику, я сократил ее до 40 секунд. Мой менеджер забрал программу домой, выполнил некоторые оптимизации работы программы и довел ее до 12 секунд. Я переупорядочил оценку строк и столбцов,
Самые быстрые наши программы получили за месяц 0,15 секунды для моего менеджера и 0,33 секунды для меня. Я закончил тем, что утверждал, что моя программа была быстрее, так как программа моего менеджера, хотя и находила все решения, не печатала их в текстовый файл. Если он добавил эту логику в свой код, это часто занимало более 0,4-0,5 секунд.
С тех пор мы позволили существовать нашему внутриличностному вызову, но, конечно, остается вопрос: можно ли сделать эту программу быстрее?
Это вызов, который я собираюсь поставить перед вами, ребята.
Ваш вызов
Параметры, с которыми мы работали, ослабили правило «сумма 29», чтобы вместо них быть «равными суммам всех строк / столбцов», и я собираюсь установить это правило и для вас, ребята. Задача, следовательно, заключается в следующем: написать программу, которая находит (и печатает!) Все решения этой загадки в кратчайшие сроки. Я собираюсь установить предел для представленных решений: если программа занимает более 10 секунд на относительно приличном компьютере (<8 лет), вероятно, она слишком медленная для подсчета.
Также у меня есть несколько бонусов за головоломку:
- Можете ли вы обобщить решение так, чтобы оно работало для любого набора из 16 чисел, а не только
int[1,16]
? Оценка времени будет оцениваться на основе исходного набора номеров подсказок, но проходить через этот кодовый путь. (-10%) - Можете ли вы написать код таким образом, чтобы он изящно обрабатывал и решал дублирующиеся числа? Это не так просто, как может показаться! Решения, которые «визуально идентичны», должны быть уникальными в наборе результатов. (-5%)
- Можете ли вы обрабатывать отрицательные числа? (-5%)
Вы также можете попытаться сгенерировать решение, которое обрабатывает числа с плавающей запятой, но, конечно, не удивляйтесь, если это не удастся полностью. Если вы найдете надежное решение, это может стоить большого бонуса!
По сути, «Вращения» считаются уникальными решениями. Таким образом, решение, представляющее собой просто вращение другого решения, считается его собственным решением.
Средами разработки, на которых я работаю, являются Java и C ++. Я могу принимать ответы на других языках, но вам может понадобиться указать ссылку, где я могу получить простую в настройке среду выполнения для вашего кода.