На собеседованиях в крупные компании любят задавать математические задачки. Обычные люди пытаются решить их на листах бумаги, но, если владеть простыми техническими навыками, вы сможете найти ответ за секунду. Для начала — задача:
Отец умер, и всё его имущество перешло к жене и детям. Среди имущества был сейф, где он держал свои самые важные бумаги и драгоценности. Детям страшно хотелось попасть в этот сейф.
У сейфа был секрет: если неправильно ввести шифр, всё содержимое сразу сжигается. Сам кодовый замок выглядел как квадрат из 16 ячеек, в которые нужно ввести числа.
Дети назвали несколько чисел, которые они запомнили, подсматривая за отцом, но остальных они не знали. Мать вспомнила странность отцовского шифра: сумма всех чисел по каждой горизонтали, вертикали и двум главным диагоналям равнялась 50. Всё усложнялось тем, что можно было вводить только числа от 5 до 20 без повторений — особенность семейного сейфа.
Помогите родственникам ввести недостающие числа с первого раза, иначе всё наследство будет уничтожено системой безопасности сейфа.
Решение
Обычно люди решают эту задачу простым перебором: подставляют цифры в разные ячейки и ищут то их сочетание, которое будет соответствовать всем условиям.
Но перебор — это идеальная задача для компьютера. Напишем простой алгоритм, который переберёт все варианты кодов и найдёт нужное сочетание.
Решим эту задачу методом полного перебора. Это значит, что мы по очереди будем подставлять вместо иксов на картинке все числа в диапазоне от 5 до 20 в каждую ячейку, а потом проверять, выполняются условия задачи или нет.
Для решения нам понадобится новый тип переменной — массив.
Попробуем объяснить, что такое массив. Представьте, что вы завскладом с продуктами на большой базе. Весь склад делится на несколько помещений, а в каждом из них — много стеллажей, выстроенных в линию и пронумерованных начиная с нуля. Программисты вообще любят вести нумерацию с нуля — так уж у них исторически сложилось.
Cтеллажи — пример одномерного массива. Одномерного — значит, что вам неважно, на какой конкретной полке лежит чеснок, достаточно знать, что он на стеллаже номер 0. На стеллаже номер 1 — конфеты, а под номером 2 — стеллаж с чаем. Этого достаточно, чтобы послать грузчика за чаем, если вам неважно, какой чай он принесёт. Одномерность означает, что нам нужно знать один только номер стеллажа, без номеров полок.
Двумерный массив — когда важно назвать грузчику не только номер стеллажа, но и номер полки, с которого взять чай. Трёхмерный — когда нужен ещё и номер нужной подсобки и так далее. Чем сложнее система размещения продуктов на складе, тем многомернее массив. Если нужный нам чай хранится в самой первой подсобке на втором стеллаже, а в нём — на четвёртой полке, то на языке JavaScript это бы выглядело так:
Склад[ подсобка_0 ][ стеллаж_1 ][ полка_3 ] = 'чай байховый';
Не забывайте, что всё считается начиная с нуля, поэтому первая подсобка превратилась в подсобку_0, а четвёртая полка — в полку_3.
Давайте посмотрим на примеры в JavaScript. Начнём с одномерного массива из шести целых чисел:
var massiv = [4,6,1,3,5,2];
Чтобы вывести такой массив на экран, используем цикл от 0 до 5 — всего 6 шагов. Помните, что нумерация в массивах начинается с нуля:
for (var i = 0; i < 6; i++) {
// каждый элемент массива — на новой строке
console.log(massiv[i]);
}
Теперь давайте попробуем сделать двумерный массив. В нём первым будет идти число, вторым — название этого числа по-русски:
var massiv_2 = [
[1,'Один'],
[2,'Два'],
[3,'Три'],
[4,'Четыре'],
[5,'Пять'],
[6,'Шесть'],
]
Чтобы вывести, например, слово «Один», нам нужен элемент под номером 0 (нумерация — с нуля, а не с единицы), а в нём — ячейка под номером 1:
console.log(massiv_2[0][1]);
А чтобы вывести число 5 — четвёртый элемент, нулевая ячейка:
console.log(massiv_2[4][0]);
Давайте выведем на экран все значения всех ячеек нашего двумерного массива. Для этого возьмём вложенный цикл: первый будет отвечать за номера элементов, второй — за номера ячеек:
//i — номер элемента, от 0 до 5
for (var i = 0; i < 6; i++) {
//j — номер ячейки, от 0 до 1
for (var j = 0; j < 2; j++) {
//на каждом шаге цикла значения i и j меняются
console.log(massiv_2[i][j]);
}
}
Вот так просто работать с массивами. Теперь мы можем использовать их для решения нашей задачи.
Решение: объявляем переменные
Первое, что нам понадобится, — массив, где будет храниться комбинация шифра. Так как мы уже знаем некоторые числа, то сразу подставим их в нужное место. Остальные сделаем нулевыми, а в процессе решения будем подставлять на их место новые значения:
var code=[
[0,15,0,5],
[17,0,11,0],
[0,0,0,0],
[14,9,0,0],
];
Смотрите, наш массив — двумерный и состоит как бы из строк и столбцов. Зная это, мы можем работать с каждым элементом массива как с ячейкой таблицы. Заведём отдельные переменные для циклов:
var i1,i2,i3,i4,i5,i6,i7,i8,i9,i10;
И сделаем отдельную переменную, которая будет следить, нашли мы решение или нет. Сделаем её сначала нулевой, а как только найдём комбинацию — запишем в неё единицу. Если к концу работы программы в переменной так и останется ноль, значит, решения мы не нашли и сейф нам не открыть.
var code_exist=0;
Логика работы и цикл
Решение будет таким:
- Так как мы не знаем 10 чисел в шифре, сделаем 10 вложенных циклов и будем из них брать значения для подстановки.
- Подставляем эти значения в наш шифр.
- Проверяем все условия, которые есть в задаче.
- Если все условия выполнились — выводим результат и помечаем в переменной, что решение найдено.
Так как у нас 10 неизвестных чисел, нам понадобится 10 циклов, вложенных друг в друга. Это нужно для того, чтобы перебрать все возможные комбинации. По условию, числа лежат в диапазоне от 5 до 20 включительно, но в таблице уже есть число 5 и повторяться оно не может. Значит, нам нужен диапазон поменьше — от 6 до 20.
Сделаем 10 вложенных циклов, используя отдельную переменную для каждого цикла. Не забывайте, нам нужны циклы с числом 20 включительно, поэтому условие цикла — переменная должна быть меньше 21.
for (i1 = 6; i1 < 21; i1++) {
for(i2 =6; i2 <21; i2++) {
for(i3 =6; i3 <21; i3++) {
for(i4 =6; i4 <21; i4++) {
for(i5 =6; i5 <21; i5++) {
for(i6 =6; i6 <21; i6++) {
for(i7 =6; i7 <21; i7++) {
for(i8 =6; i8 <21; i8++) {
for(i9 =6; i9 <21; i9++) {
for(i10 =6; i10 <21; i10++) {
}
}
}
}
}
}
}
}
}
}
console.log('i1= ' + i1);
В предпоследнем цикле мы добавили вывод текущего значения переменной i1. Это связано с тем, что полный перебор — ресурсоёмкая задача, которая может занимать десятки минут. Чтобы не было ощущения, что компьютер завис, будем по очереди выводить значение первой переменной — её изменение покажет, что задача всё ещё решается.
Все остальные команды будем помещать внутрь самого последнего, десятого цикла.
Подстановка значений в шифр
После каждого шага берём значения всех переменных и подставляем их на нужные места вместо нулей в наш шифр. Помните, что нумерация строк и столбцов начинается с нуля, а не с единицы:
code[0][0]=i1;
code[0][2]=i2;
code[1][1]=i3;
code[1][3]=i4;
code[2][0]=i5;
code[2][1]=i6;
code[2][2]=i7;
code[2][3]=i8;
code[3][2]=i9;
code[3][3]=i0;
Проверяем полученное решение
На каждом шаге цикла нужно проверять, соответствует ли наше решение начальным условиям. Напомним условия:
- числа от 5 до 20 включительно;
- каждое число можно использовать только один раз;
- сумма всех чисел по всем горизонталям, вертикалям и двум главным диагоналям должна равняться 50.
Сделаем все эти проверки внутри одного условия. Начнём с проверки всех горизонталей:
(code[0][0]+code[0][1]+code[0][2]+code[0][3] == 50) &&
(code[1][0]+code[1][1]+code[1][2]+code[1][3] == 50) &&
(code[2][0]+code[2][1]+code[2][2]+code[2][3] == 50) &&
(code[3][0]+code[3][1]+code[3][2]+code[3][3] == 50)
Затем вертикалей:
(code[0][0]+code[1][0]+code[2][0]+code[3][0] == 50) &&
(code[0][1]+code[1][1]+code[2][1]+code[3][1] == 50) &&
(code[0][2]+code[1][2]+code[2][2]+code[3][2] == 50) &&
(code[0][3]+code[1][3]+code[2][3]+code[3][3] == 50)
Проверим диагонали:
(code[0][0]+code[1][1]+code[2][2]+code[3][3] == 50) &&
(code[0][3]+code[1][2]+code[2][1]+code[3][0] == 50)
И проверим, чтобы не было двух одинаковых чисел. Для этого по очереди сравним каждое значение элемента шифра со всеми остальными по очереди. Этот код не очень элегантный, но делать элегантно сейчас не хочется, ведь мы хотим заставить работать машину, а не себя.
(code[0][0] != code[0][1]) && (code[0][0] != code[0][2]) && (code[0][0] != code[0][3]) && (code[0][0] != code[1][0]) && (code[0][0] != code[1][1]) && (code[0][0] != code[1][2]) && (code[0][0] != code[1][3]) && (code[0][0] != code[2][0]) && (code[0][0] != code[2][1]) && (code[0][0] != code[2][2]) && (code[0][0] != code[2][3]) && (code[0][0] != code[3][0]) && (code[0][0] != code[3][1]) && (code[0][0] != code[3][2]) && (code[0][0] != code[3][3]) &&
(code[0][1] != code[0][2]) && (code[0][1] != code[0][3]) && (code[0][1] != code[1][0]) && (code[0][1] != code[1][1]) && (code[0][1] != code[1][2]) && (code[0][1] != code[1][3]) && (code[0][1] != code[2][0]) && (code[0][1] != code[2][1]) && (code[0][1] != code[2][2]) && (code[0][1] != code[2][3]) && (code[0][1] != code[3][0]) && (code[0][1] != code[3][1]) && (code[0][1] != code[3][2]) && (code[0][1] != code[3][3]) &&
(code[0][2] != code[0][3]) && (code[0][2] != code[1][0]) && (code[0][2] != code[1][1]) && (code[0][2] != code[1][2]) && (code[0][2] != code[1][3]) && (code[0][2] != code[2][0]) && (code[0][2] != code[2][1]) && (code[0][2] != code[2][2]) && (code[0][2] != code[2][3]) && (code[0][2] != code[3][0]) && (code[0][2] != code[3][1]) && (code[0][2] != code[3][2]) && (code[0][2] != code[3][3]) &&
(code[0][3] != code[1][0]) && (code[0][3] != code[1][1]) && (code[0][3] != code[1][2]) && (code[0][3] != code[1][3]) && (code[0][3] != code[2][0]) && (code[0][3] != code[2][1]) && (code[0][3] != code[2][2]) && (code[0][3] != code[2][3]) && (code[0][3] != code[3][0]) && (code[0][3] != code[3][1]) && (code[0][3] != code[3][2]) && (code[0][3] != code[3][3]) &&
(code[1][0] != code[1][1]) && (code[1][0] != code[1][2]) && (code[1][0] != code[1][3]) && (code[1][0] != code[2][0]) && (code[1][0] != code[2][1]) && (code[1][0] != code[2][2]) && (code[1][0] != code[2][3]) && (code[1][0] != code[3][0]) && (code[1][0] != code[3][1]) && (code[1][0] != code[3][2]) && (code[1][0] != code[3][3]) &&
(code[1][1] != code[1][2]) && (code[1][1] != code[1][3]) && (code[1][1] != code[2][0]) && (code[1][1] != code[2][1]) && (code[1][1] != code[2][2]) && (code[1][1] != code[2][3]) && (code[1][1] != code[3][0]) && (code[1][1] != code[3][1]) && (code[1][1] != code[3][2]) && (code[1][1] != code[3][3]) &&
(code[1][2] != code[1][3]) && (code[1][2] != code[2][0]) && (code[1][2] != code[2][1]) && (code[1][2] != code[2][2]) && (code[1][2] != code[2][3]) && (code[1][2] != code[3][0]) && (code[1][2] != code[3][1]) && (code[1][2] != code[3][2]) && (code[1][2] != code[3][3]) &&
(code[1][3] != code[2][0]) && (code[1][3] != code[2][1]) && (code[1][3] != code[2][2]) && (code[1][3] != code[2][3]) && (code[1][3] != code[3][0]) && (code[1][3] != code[3][1]) && (code[1][3] != code[3][2]) && (code[1][3] != code[3][3]) &&
(code[2][0] != code[2][1]) && (code[2][0] != code[2][2]) && (code[2][0] != code[2][3]) && (code[2][0] != code[3][0]) && (code[2][0] != code[3][1]) && (code[2][0] != code[3][2]) && (code[2][0] != code[3][3]) &&
(code[2][1] != code[2][2]) && (code[2][1] != code[2][3]) && (code[2][1] != code[3][0]) && (code[2][1] != code[3][1]) && (code[2][1] != code[3][2]) && (code[2][1] != code[3][3]) &&
(code[2][2] != code[2][3]) && (code[2][2] != code[3][0]) && (code[2][2] != code[3][1]) && (code[2][2] != code[3][2]) && (code[2][2] != code[3][3]) &&
(code[2][3] != code[3][0]) && (code[2][3] != code[3][1]) && (code[2][3] != code[3][2]) && (code[2][3] != code[3][3]) &&
(code[3][0] != code[3][1]) && (code[3][0] != code[3][2]) && (code[3][0] != code[3][3]) &&
(code[3][1] != code[3][2]) && (code[3][1] != code[3][3]) &&
(code[3][2] != code[3][3])
Выводим правильную комбинацию
Сейчас наш условный оператор пустой: если все условия выполнятся, то всё равно ничего не произойдёт. Давайте это исправим и добавим вывод решения на экран:
console.log('Unlocked!');
console.log(code[0][0]+' '+code[0][1]+' '+code[0][2]+' '+code[0][3]);
console.log(code[1][0]+' '+code[1][1]+' '+code[1][2]+' '+code[1][3]);
console.log(code[2][0]+' '+code[2][1]+' '+code[2][2]+' '+code[2][3]);
console.log(code[3][0]+' '+code[3][1]+' '+code[3][2]+' '+code[3][3]);
Собираем код в одно целое
Чтобы программа работала правильно, надо все команды записать в нужной последовательности:
var code = [
[0, 15, 0, 5],
[17, 0, 11, 0],
[0, 0, 0, 0],
[14, 9, 0, 0],
];
var i1, i2, i3, i4, i5, i6, i7, i8, i9, i10;
var code_exist = 0;
for (i1 = 6; i1 < 21; i1++) {
for (i2 = 6; i2 < 21; i2++) {
for (i3 = 6; i3 < 21; i3++) {
for (i4 = 6; i4 < 21; i4++) {
for (i5 = 6; i5 < 21; i5++) {
for (i6 = 6; i6 < 21; i6++) {
for (i7 = 6; i7 < 21; i7++) {
for (i8 = 6; i8 < 21; i8++) {
for (i9 = 6; i9 < 21; i9++) {
for (i10 = 6; i10 < 21; i10++) {
code[0][0] = i1;
code[0][2] = i2;
code[1][1] = i3;
code[1][3] = i4;
code[2][0] = i5;
code[2][1] = i6;
code[2][2] = i7;
code[2][3] = i8;
code[3][2] = i9;
code[3][3] = i10;
if (
(code[0][0] + code[0][1] + code[0][2] + code[0][3] == 50) &&
(code[1][0] + code[1][1] + code[1][2] + code[1][3] == 50) &&
(code[2][0] + code[2][1] + code[2][2] + code[2][3] == 50) &&
(code[3][0] + code[3][1] + code[3][2] + code[3][3] == 50) &&
(code[0][0] + code[1][0] + code[2][0] + code[3][0] == 50) &&
(code[0][1] + code[1][1] + code[2][1] + code[3][1] == 50) &&
(code[0][2] + code[1][2] + code[2][2] + code[3][2] == 50) &&
(code[0][3] + code[1][3] + code[2][3] + code[3][3] == 50) &&
(code[0][0] + code[1][1] + code[2][2] + code[3][3] == 50) &&
(code[0][3] + code[1][2] + code[2][1] + code[3][0] == 50) &&
(code[0][0] != code[0][1]) && (code[0][0] != code[0][2]) && (code[0][0] != code[0][3]) && (code[0][0] != code[1][0]) && (code[0][0] != code[1][1]) && (code[0][0] != code[1][2]) && (code[0][0] != code[1][3]) && (code[0][0] != code[2][0]) && (code[0][0] != code[2][1]) && (code[0][0] != code[2][2]) && (code[0][0] != code[2][3]) && (code[0][0] != code[3][0]) && (code[0][0] != code[3][1]) && (code[0][0] != code[3][2]) && (code[0][0] != code[3][3]) &&
(code[0][1] != code[0][2]) && (code[0][1] != code[0][3]) && (code[0][1] != code[1][0]) && (code[0][1] != code[1][1]) && (code[0][1] != code[1][2]) && (code[0][1] != code[1][3]) && (code[0][1] != code[2][0]) && (code[0][1] != code[2][1]) && (code[0][1] != code[2][2]) && (code[0][1] != code[2][3]) && (code[0][1] != code[3][0]) && (code[0][1] != code[3][1]) && (code[0][1] != code[3][2]) && (code[0][1] != code[3][3]) &&
(code[0][2] != code[0][3]) && (code[0][2] != code[1][0]) && (code[0][2] != code[1][1]) && (code[0][2] != code[1][2]) && (code[0][2] != code[1][3]) && (code[0][2] != code[2][0]) && (code[0][2] != code[2][1]) && (code[0][2] != code[2][2]) && (code[0][2] != code[2][3]) && (code[0][2] != code[3][0]) && (code[0][2] != code[3][1]) && (code[0][2] != code[3][2]) && (code[0][2] != code[3][3]) &&
(code[0][3] != code[1][0]) && (code[0][3] != code[1][1]) && (code[0][3] != code[1][2]) && (code[0][3] != code[1][3]) && (code[0][3] != code[2][0]) && (code[0][3] != code[2][1]) && (code[0][3] != code[2][2]) && (code[0][3] != code[2][3]) && (code[0][3] != code[3][0]) && (code[0][3] != code[3][1]) && (code[0][3] != code[3][2]) && (code[0][3] != code[3][3]) &&
(code[1][0] != code[1][1]) && (code[1][0] != code[1][2]) && (code[1][0] != code[1][3]) && (code[1][0] != code[2][0]) && (code[1][0] != code[2][1]) && (code[1][0] != code[2][2]) && (code[1][0] != code[2][3]) && (code[1][0] != code[3][0]) && (code[1][0] != code[3][1]) && (code[1][0] != code[3][2]) && (code[1][0] != code[3][3]) &&
(code[1][1] != code[1][2]) && (code[1][1] != code[1][3]) && (code[1][1] != code[2][0]) && (code[1][1] != code[2][1]) && (code[1][1] != code[2][2]) && (code[1][1] != code[2][3]) && (code[1][1] != code[3][0]) && (code[1][1] != code[3][1]) && (code[1][1] != code[3][2]) && (code[1][1] != code[3][3]) &&
(code[1][2] != code[1][3]) && (code[1][2] != code[2][0]) && (code[1][2] != code[2][1]) && (code[1][2] != code[2][2]) && (code[1][2] != code[2][3]) && (code[1][2] != code[3][0]) && (code[1][2] != code[3][1]) && (code[1][2] != code[3][2]) && (code[1][2] != code[3][3]) &&
(code[1][3] != code[2][0]) && (code[1][3] != code[2][1]) && (code[1][3] != code[2][2]) && (code[1][3] != code[2][3]) && (code[1][3] != code[3][0]) && (code[1][3] != code[3][1]) && (code[1][3] != code[3][2]) && (code[1][3] != code[3][3]) &&
(code[2][0] != code[2][1]) && (code[2][0] != code[2][2]) && (code[2][0] != code[2][3]) && (code[2][0] != code[3][0]) && (code[2][0] != code[3][1]) && (code[2][0] != code[3][2]) && (code[2][0] != code[3][3]) &&
(code[2][1] != code[2][2]) && (code[2][1] != code[2][3]) && (code[2][1] != code[3][0]) && (code[2][1] != code[3][1]) && (code[2][1] != code[3][2]) && (code[2][1] != code[3][3]) &&
(code[2][2] != code[2][3]) && (code[2][2] != code[3][0]) && (code[2][2] != code[3][1]) && (code[2][2] != code[3][2]) && (code[2][2] != code[3][3]) &&
(code[2][3] != code[3][0]) && (code[2][3] != code[3][1]) && (code[2][3] != code[3][2]) && (code[2][3] != code[3][3]) &&
(code[3][0] != code[3][1]) && (code[3][0] != code[3][2]) && (code[3][0] != code[3][3]) &&
(code[3][1] != code[3][2]) && (code[3][1] != code[3][3]) &&
(code[3][2] != code[3][3])
) {
console.log('Unlocked!');
console.log(code[0][0] + ' ' + code[0][1] + ' ' + code[0][2] + ' ' + code[0][3]);
console.log(code[1][0] + ' ' + code[1][1] + ' ' + code[1][2] + ' ' + code[1][3]);
console.log(code[2][0] + ' ' + code[2][1] + ' ' + code[2][2] + ' ' + code[2][3]);
console.log(code[3][0] + ' ' + code[3][1] + ' ' + code[3][2] + ' ' + code[3][3]);
}
}
}
}
}
}
}
}
}
}
console.log('i1= ' + i1);
}
Запускаем код в браузере
Запускаем браузер Chrome и вызываем консоль комбинацией клавиш Shift+Ctrl+I
. Вставляем наш код в консоль, нажимаем Enter. Через несколько минут вы увидите начало работы программы — начнётся вывод значений переменной i1
. Как только компьютер подберёт решение — тут же выведет его на экран и продолжит работу. Если решение не поместилось на экране — просто прокрутите вывод результатов наверх.
И ТУТ ПРИБЕГАЮТ НАСТОЯЩИЕ ПРОГРАММИСТЫ и кричат «ПЛОХОЙ КОД!»
Тот код, который у нас получился — примитивный, плохо выглядит и вызывает сильное желание его оптимизировать. Давайте перепишем кусок кода с проверками, где мы в лоб сравниваем элементы массива между собой и проверяем таким же образом сумму элементов в строках.
// сигнальные переменные, которые следят за тем, все ли условия выполняются. По умолчанию они говорят, что всё верно, но внутри кода, как только будет ошибка при проверке, они превратятся в ложные
var diag = true;
var lines = true;
var uniqie = true;
// здесь мы будем хранить результат вычислений сумм, равны они 50 или нет
var result1, result2, result3, result4 = 0;
// проверяем обе диагонали
for (var j = 0; j < 4; j++) {
result1 += code[j][j];
result2 += code[j][3 - j];
}
// если хоть одно из них не 50 — помечаем, что условие не выполнилось
if ((result1 != 50) && (result2 != 50)) { diag = false };
// проверяем сумму в строках и столбцах
for (var j = 0; j < 4; j++) {
for (var k = 0; k < 4; k++) {
// суммируем строку и столбец
result3 += code[j][k];
result4 += code[k][j];
}
// если сумма не та, что нужно — помечаем, что условие в линиях не выполнилось
if (result3 != 50) { lines = false };
if (result4 != 50) { lines = false };
// здесь же — сравниваем каждый элемент массива со всеми остальными
for (j1 = j; j1 < 4; j1++) {
for (k1 = k; k1 < 4; k1++) {
// если нашли равные элементы — помечаем, что эта проверка провалилась
if (code[j][k] == code[j1][k1]) { uniqie = false };
}
}
}
// если все проверки прошли успешно — выводим результат
if (diag && lines && uniqie) {
console.log('Unlocked!');
console.log(code[0][0] + ' ' + code[0][1] + ' ' + code[0][2] + ' ' + code[0][3]);
console.log(code[1][0] + ' ' + code[1][1] + ' ' + code[1][2] + ' ' + code[1][3]);
console.log(code[2][0] + ' ' + code[2][1] + ' ' + code[2][2] + ' ' + code[2][3]);
console.log(code[3][0] + ' ' + code[3][1] + ' ' + code[3][2] + ' ' + code[3][3]);
}
Код стал читабельнее, ушли громадные проверки элементов массива, но этот код стал выполняться в 40-50 раз медленнее, чем старый! Дело в том, что у нас при проверке появилось несколько двойных циклов, а в месте, где мы проверяем элементы массива на уникальность — аж 4 вложенных цикла. При этом они все располагаются внутри десятикратно вложенного цикла, который сам по себе — достаточно ресурсоёмкая вещь.
Поэтому, не всегда нужно оптимизировать код, который внешне выглядит некрасиво. Если на первом месте — быстродействие, то иногда грубый код работает быстрее, чем красивый. Такие дела.
А где ответ на задачу?
А ответа не будет :-) Чтобы его узнать, запустите код самостоятельно. Например, через консоль браузера. И дождитесь, когда код выдаст правильный ответ.
Что дальше
Код, который у нас получился, — не самый красивый и быстрый, его можно оптимизировать несколькими способами. Например, отказаться от промежуточного заполнения переменной code
и сравнивать значения переменных напрямую. Есть и другие, более изящные способы организации полного перебора.