Задача по графам (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 шт):

Автор решения: Stanislav Volodarskiy

Всё у вас хорошо, кроме двух вещей: медленного ввода и медленной очереди.

Ввод

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

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

→ Ссылка