今回は paiza の「いびつなひとりリバーシ(1ターン)」の問題に挑戦!
🧩 問題概要
-
H×Wの盤面が与えられる - 盤面には次の4種類のマスがある
-
'*':すでに置かれている自分の石 -
'.':何もないマス -
'#':穴の空いているマス(通過不可) -
'!':今回、新しく石を置くマス(1つだけ)
-
プレイヤーの操作ルール
-
'!'のマスに 石'*'を1つ置く - その石を起点にして縦・横・斜めの8方向 に探索を行う
- ある方向について次を満たす場合、石を置ける
- 新しく置いた石
'*'と既存の石'*'によって、穴'#'を含まない連続したマス をはさめている
- 新しく置いた石
- はさめた場合
→ その間にあるマスをすべて'*'にする - 新たに石を置いた結果、さらに置ける場合があっても追加の操作は行わない(1回で終了)
最終的にやること
- 上記の操作を 1回だけ 行ったあとの盤面を
H行でそのまま出力する
入力例:
5 5
*.*.*
.....
*#!.*
...#*
*.***
出力例:
*.*.*
.***.
*#***
.**#*
*.***
✅OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (line) => lines.push(line));
rl.on('close', () => {
const [H, W] = lines[0].split(' ').map(Number);
const grid = lines.slice(1).map(row => row.split(''));
// 方向リスト
const directionList = [
[-1, 0], // 上
[-1, 1], // 右上
[0, 1], // 右
[1, 1], // 右下
[1, 0], // 下
[1, -1], // 左下
[0, -1], // 左
[-1, -1] // 左上
];
// 石を置く位置
let X;
let Y;
LABEL:
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
if (grid[y][x] === '!') {
X = x;
Y = y;
break LABEL;
}
}
}
// 石を置く
grid[Y][X] = '*';
// ひっくり返す
for (const direction of directionList) {
const [dy, dx] = direction;
let ny = Y + dy;
let nx = X + dx;
const path = [];
while (0 <= ny && ny < H && 0 <= nx && nx < W) {
// 穴の開いているマス → 探索終了
if (grid[ny][nx] === '#') {
break;
}
// はさめる → ひっくりかえす
if (grid[ny][nx] === '*') {
for (const [py, px] of path) {
grid[py][px] = '*';
}
break;
}
path.push([ny, nx]);
ny += dy;
nx += dx;
}
}
// 出力
grid.forEach(g => console.log(g.join('')));
});
🔍 コードの流れ
- 標準入力を1行ずつ読み込み、配列
linesに保存する - 1行目から盤面サイズ
H,Wを取得する - 続く
H行から盤面を読み込み、2次元配列gridを作成する - 縦・横・斜めの 8方向 を
(dy, dx)の配列directionListとして定義する - 盤面全体を走査し、石を置くマス
'!'の座標(Y, X)を見つける -
'!'の位置に石'*'を置く - 各方向について次の処理を行う
-
(Y, X)の1マス先から探索を開始する - 通過したマスを
pathに順番に保存する - 探索中に
-
'#'に当たったら → その方向は失敗 -
'*'に当たったら → はさみ成功
→path内のマスをすべて'*'にする
-
- 盤面の外に出たら → その方向は何もしない
-
- 8方向すべての処理が終わったら、更新後の盤面を1行ずつ出力する
📝まとめ
🔹 1. 2次元配列の基本操作
- 入力を 2次元配列(配列の配列) に変換する
- (y, x) 座標でマスを扱う
🔹 2. 方向による探索
- 8方向を
(dy, dx)の 配列 で管理することで、同じロジックをfor文1本で回せる - 条件分岐を増やさず、実装をシンプルにできる
🔹 3. 「はさめたらひっくり返す」の考え方
- すぐに盤面を書き換えない
- まず
pathに通過したマスを保存 - 最後に
'*'に当たったときだけ反映
→ 失敗時に元に戻す必要がない
🔹 4. 探索の終了条件を明確にする
- 盤面外に出たら終了
-
'#'に当たったら失敗で終了 -
'*'に当たったらひっくり返して終了