Задача по графам (BFS)
Подскажите, пожалуйста, как решить вот такую задачку на графы:
Условия:
Дан массив из целых чисел (
-100 000 000 ≤ arr[i] ≤ 100 000 000
).Можно перемещаться по массиву вперед и назад на один шаг (
i - 1
илиi + 1
).Также можно перемещаться по массиву по одинаковым числам в любом направлении за один шаг.
Задача:
Найти минимальное число шагов от первого числа к последнему.
Входные данные:
На вход подаётся строка, содержащая массив целых чисел длинной
n
(1 ≤ n ≤ 50 000
)
Пример 1:
Ввод:
4 6 3 3 6
Вывод:
2
Объяснение:
С первого числа (
4
) можно попасть только на второе (6
). Со второго (6
) можно переместиться на пятое (6
), так как числа одинаковые.Минимальное количество шагов:
2
.Пример 2:
Ввод:
31 -6 -6 21 31 8 8 8 5 21
Вывод:
3
Объяснение:
С первого (
31
) перемещаемся на пятое (31
), шаг назад до четвертого (21
), потом до последнего (21
).Пример 3:
Ввод:
7 6 1 6 1 6 1 7
Вывод:
1
Объяснение:
С первого (
7
) перемещаемся на последнее (7
).
Я написал вот такой код на NodeJS
, но в песочнице ответ не проходит именно из-за превышения лимита времени выполнения:
const readline = require('readline').createInterface(process.stdin, process.stdout);
function connect(graph, i, j) {
graph[i].push(j);
graph[j].push(i);
}
readline.on('line', (line) => {
const islands = line.trim().split(' ').map(Number);
const n = islands.length;
// Создание графа
const graph = Array.from({ length: 2 * n - 1 }, () => []);
// Подключение соседних островов
for (let j = 0; j < 2 * n - 2; ++j) {
connect(graph, j, j + 1);
}
// Создание порталов (материков)
const continents = new Map();
for (let i = 0; i < n; ++i) {
const c = islands[i];
if (!continents.has(c)) {
continents.set(c, graph.length);
graph.push([]);
}
connect(graph, 2 * i, continents.get(c));
}
const distance = new Array(graph.length).fill(0);
const queue = [0]; // Можно использовать массив как очередь
// BFS
while (queue.length > 0) {
const j = queue.shift(); // Извлекаем первый элемент очереди
if (j === 2 * n - 2) {
break;
}
for (const k of graph[j]) {
if (distance[k] === 0) {
distance[k] = distance[j] + 1;
queue.push(k);
}
}
}
console.log(String(distance[2 * n - 2] / 2));
readline.close();
}).on('close', () => process.exit(0));
Кто нибудь может, пожалуйста, помочь оптимизировать решение?
Ответы (1 шт):
Всё у вас хорошо, кроме двух вещей: медленного ввода и медленной очереди.
Ввод
const readline = require('readline').createInterface(process.stdin, process.stdout);
Если убрать второй аргумент, всё начинает летать:
const readline = require('readline').createInterface(process.stdin);
$ time seq -s' ' 1 50000 | node temp.js 49999 real 0m0.162s user 0m0.196s sys 0m0.024s
Почему? Беглый просмотр исходных кодов (https://github.com/nodejs/node/blob/main/lib/internal/readline/interface.js) мало что дал, но из чтения документации вокруг можно догадаться что readline
пытается организовать собственный редактор командной строки, который тормозит на длинных строках. Если ему не дать output
, он этого не делает и читает данные быстро. Кстати, вы заметили, что ваша программа зачем-то печатает копию входа?
$ echo 7 6 1 6 1 6 1 7 | node temp.js 7 6 1 6 1 6 1 7 1
Вот это оно и есть.
Очередь
JavaScript не гарантирует константное время для операции
Array.prototype.shift()
.
Строка с комментарием тормозит для длинных очередей:
...
while (queue.length > 0) {
const j = queue.shift(); // Извлекаем первый элемент очереди
...
$ time node -e "console.log('1 '.repeat(50000))" | node temp.js 1 real 0m0.915s user 0m0.976s sys 0m0.028s
Если избавиться от shift
и сделать скользящую голову очереди, скорость вырастет:
...
let head = 0;
while (queue.length > head) {
const j = queue[head++]; // Извлекаем первый элемент очереди
...
$ time node -e "console.log('1 '.repeat(50000))" | node temp.js 1 real 0m0.137s user 0m0.192s sys 0m0.032s
В последнем случае очередь стала длиннее, но это не более пятидесяти тысяч элементов. Такого же размера строка ввода, никто не заметит. А вот изменение скорости может быть заметно.