今回は paiza の「格闘ゲーム」の問題に挑戦!
スライドウィンドウのアルゴリズムを自然と使えるようになった!
📘問題概要
格闘ゲームで、特定のコマンド入力(5文字のボタン入力)に応じて技を出したい。
◯ 技:コマンド
- rolling:"LLLRB"
- upper:"DDRRA"
- rush:"AAAAA"
◯ 入力
- 1行目:長いボタン入力の文字列
S
◯ やること
-
Sを左から順に見て、5文字のコマンドが一致したらその技を発動(出力)する。 - 一致した5文字は“消費される”ので、次の検索はその5文字後から始まる。
- コマンドが一致しない場合は、1文字右にずらして再チェック。
- 最後まで見て、発動した技を順に出力。
※ 注意点
- 一つのボタン入力は一つのコマンドだけに使える。
- 例:
DDRRAAAAA- 「
DDRRA」→upperを発動 - 残り「
AAAA」 - 「
AAAAA」が無いのでrushは出ない
- 「
- 例:
入力例:
DDRRAAAAALLLRB
出力例:
upper
rolling
✅ OK例:
const rl = require('readline').createInterface({ input :process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const s = lines[0];
let start = 0;
let end = 5;
while (end <= s.length) {
const command = s.slice(start, end);
if (command === "LLLRB") {
console.log("rolling");
start = end;
end = end + 5;
} else if (command === "DDRRA") {
console.log("upper");
start = end;
end = end + 5;
} else if (command === "AAAAA") {
console.log("rush")
start = end;
end = end + 5;
} else {
start++;
end++;
}
}
});
🔍 コードの流れ
-
標準入力から文字列
Sを受け取る準備をする。 -
入力が完了したら(
close)、
1行目の文字列(S)をsに代入。 -
start = 0、end = 5で
最初の 5 文字のウィンドウ(区間)を作る。 -
while文で
endが文字列の長さ以内の間、ずっとループ。 -
s.slice(start, end)で
現在の 5 文字をcommandとして取り出す。 -
その 5 文字が
"LLLRB" → rolling
"DDRRA" → upper
"AAAAA" → rush
のどれかと一致するか判定。 -
一致した場合:
- 対応する技名を出力
- 使った5文字は消費される扱い →
start = end-
end = end + 5
(次の5文字区間に一気にジャンプ)
-
一致しなかった場合:
- コマンドではないので
start++-
end++
→ 1文字ずつ右にスライドして再チェック
- コマンドではないので
-
endが文字列長を超えたらループ終了。 -
終了。
✨ 改善版
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const s = lines[0];
const moves = {
"LLLRB": "rolling",
"DDRRA": "upper",
"AAAAA": "rush"
};
let start = 0;
let end = 5;
while (end <= s.length) {
const command = s.slice(start, end);
if (moves[command]) {
console.log(moves[command]);
start = end;
end = start + 5;
} else {
start++;
end++;
}
}
});
💡 ポイント
① 条件分岐(if 〜 else if)が1つに集約された
- "LLLRB", "DDRRA", "AAAAA" の判定を
オブジェクトmovesにまとめた。 - 結果的に
if (moves[command])だけで判定が完結。 - コードが 短くなる・読みやすい・追加が簡単 になった。
② コマンド→技名の対応をデータとして管理
- コマンド文字列と技名の対応を
const moves = { "LLLRB": "rolling", ... }
という辞書(オブジェクト)にまとめた
- 新しい技を追加するときは
moves["XXXXX"] = "newMove",
だけでOK
③ 出力の簡潔化
console.log(moves[command]);
でそのまま技名を出力できるため、以前のような長い if 文が不要。
④ 「使った文字を5文字飛ばす」処理が明確に
- コマンド一致時:
start = end;
end = start + 5;
と書いており、「5文字消費して次へ」 が、以前より読みやすく、ミスしにくい形に改善。
🗒️ まとめ
🔍 スライドウィンドウ(5文字区間)でチェック
-
startからend = start + 5までの5文字を切り取って判定。- コマンド一致 → 5文字まとめて消費(
start=end) - 不一致 → 1文字スライド(
start++)
- コマンド一致 → 5文字まとめて消費(
🔍 オブジェクトを「辞書」として使うと判定が超スッキリ
const moves = {
"LLLRB": "rolling",
"DDRRA": "upper",
"AAAAA": "rush"
};
- キー:コマンド
- 値:技名
→moves[command]の有無で判定ができる。
🔍 if 文が大量に並ぶのを防げる
- 改善版では
if (moves[command])の1行で判定が完了。 - 可読性アップ・追加が簡単・ミスが減る。
🔍 コマンド一致時に5文字飛ばす仕組みが、とても大切
- コマンドは オーバーラッピング してはいけないため、
start = endを行って次へ進む。
🔍 文字列の部分取り出しは .slice(start, end)
- JS 標準で扱いやすい
-
endは「含まれない」ことに注意(半開区間)