【JavaScript】腐ったトマトが全体に広がるまでの最小時間を求める問題をBFSで解く
問題概要
数値の2次元配列 arr を唯一の引数として受け取る JavaScript の関数を作成します。配列の各セルには、以下のいずれかの値が格納されています。
- 0 … 空のセル
- 1 … 新鮮なトマト
- 2 … 腐ったトマト
毎分、腐ったトマトと上下左右(4方向)で隣接している新鮮なトマトは腐っていきます。
この関数は、新鮮なトマトが1つも残らなくなるまでにかかる最小の経過時間(分)を返す必要があります。もしすべてのトマトを腐らせることが不可能な場合は、代わりに -1 を返してください。
入力例
const arr = [ [2, 1, 1], [1, 1, 0], [0, 1, 1] ];
この場合、期待される出力は次のとおりです。
const output = 4;
出力の解説
腐敗は毎分1段階ずつ隣接する新鮮なトマトへ広がり、この例では4分ですべてのトマトが腐ります。経過時間ごとのグリッドの状態変化は以下の通りです。
| 経過時間(分) | トマトの状態 |
|---|---|
| 1 | [ [2, 2, 1], [2, 1, 0], [0, 1, 1] ] |
| 2 | [ [2, 2, 2], [2, 2, 0], [0, 1, 1] ] |
| 3 | [ [2, 2, 2], [2, 2, 0], [0, 2, 1] ] |
| 4 | [ [2, 2, 2], [2, 2, 0], [0, 2, 2] ] |
実装コード(BFSによる解法)
この種の「感染が広がっていく」問題は、幅優先探索(BFS)を使うことで効率的に解けます。考え方はシンプルで、まず新鮮なトマトの総数と、初期状態で腐っているトマトの座標をすべて記録します。その後、腐ったトマトを起点(複数のスタート地点)として、1分ごとに一斉に腐敗を周囲へ広げていくイメージです。
const arr = [
[2, 1, 1],
[1, 1, 0],
[0, 1, 1]
];
const timeToRot = (arr = []) => {
let fresh = 0;
let count = -1;
let curr = [];
// 初回スキャン:新鮮なトマトの数と腐ったトマトの位置を記録
for(let i = 0; i < arr.length; i++){
for(let j = 0; j < arr[i].length; j++){
if(arr[i][j] === 1){
fresh += 1;
};
if(arr[i][j] === 2){
curr.push([i, j]);
};
};
};
// 新鮮なトマトが最初から存在しない場合は 0 を返す
if(!fresh){
return 0;
};
// 多始点BFS:1分ごとに腐敗を広げる
while(curr.length > 0){
count += 1;
const next = [];
const rotten = (i, j) => {
arr[i][j] = 2
next.push([i, j])
fresh -= 1
};
for(const [i, j] of curr){
if (arr[i - 1] && arr[i - 1][j] === 1) {
rotten(i - 1, j);
};
if (arr[i + 1] && arr[i + 1][j] === 1) {
rotten(i + 1, j);
};
if (arr[i][j - 1] === 1) {
rotten(i, j - 1);
};
if (arr[i][j + 1] === 1) {
rotten(i, j + 1);
};
}
curr = next
};
// すべて腐ったなら経過分数、届かなかったトマトがあれば -1
return fresh === 0 ? count : -1;
};
console.log(timeToRot(arr));
アルゴリズムのポイント
- 初回の走査で、新鮮なトマトの総数(
fresh)と腐ったトマトの初期座標(curr)をまとめて収集します。 currをキューとして扱い、「現在腐っているトマト」を1分単位で一括処理します。これはいわゆる多始点BFSの手法です。- 上下左右の4方向をチェックし、隣接する新鮮なトマトがあれば腐らせて、次の世代のキュー(
next)に追加します。 - ループ終了後、
freshが 0 になっていれば経過分数(count)を返し、空きセルなどで隔離されて腐らなかったトマトが残っていれば-1を返します。
計算量はグリッドのサイズを M×N とした場合 O(M×N) に収まるため、大きな入力に対しても非常に効率的に動作します。
出力結果
コンソールには次のように表示されます。
4
-
JavaScriptで解く会議室スケジューリング問題 ― 時間の重複を検出する方法
問題の概要 ここでは、配列の中にさらに配列が格納されたデータ構造を扱います。各サブ配列は必ず2つの要素で構成されており、それぞれ会議の開始時刻と終了時刻を表しています。 私たちの関数に課せられたタスクは、1人の人が時間のコンフリクト(重複)を避けて出席できる会議を判定することです。具体的には、与えられたすべての会議に時間の重なりなく出席できる場合はtrue、そうでなければfalseを返します。 例として、会議の時刻を表す入力配列が次の場合を考えてみましょう。 const arr = [[5, 40], [10, 20], [25, 35]]; この場合、出力は次のようになります。 const
-
JavaScriptで解くバナナ配布問題:配列を使った実装方法を解説
問題 n人が一列に並んでいるとします。この人たちに対して、次のルールに従ってバナナを配布していきます。 まず、1人目に1本、2人目に2本…というように順番に渡し、n人目にn本を渡します。 次に、列の先頭に戻り、1人目にn+1本、2人目にn+2本…というように渡し、n人目に2n本を渡します。 このプロセスを、バナナがなくなるまで繰り返します(毎回渡す本数を1本ずつ増やし、列の末尾に達したら先頭に戻ります)。途中でバナナが足りなくなった場合は、残りの本数をすべてその時点の相手に渡します。 ここで求められているのは、人数 num を第1引数、バナナの総本数 m を第2引数として受け取るJava