今回は paiza の「リバーシの操作(縦横)」の問題に挑戦!
問題概要
- 縦
H行、横W列の盤面が与えられる - 盤面にはすでに
-
'*':自分の石が置かれているマス - '.'`:何も置かれていないマス
が存在する
-
- 指定されたマス (
Y,X) は必ず'.'であり、ここに 新しく石を1つ置く - 石を置いたあと、以下のルールで石を増やす
石を増やすルール
- 新しく置いた石 (
Y,X) から - 上・下・左・右 の4方向をそれぞれ調べる
- ある方向について
-
'*' → '.' → '.' → … → '*'のように、両端が自分の石'*'ではさまれている 場合、
→ その間にある'.'をすべて'*'にする
-
- 盤面の外に出る、または
'*'に到達できなかった方向は何もしない - 連鎖的に増えた石による追加処理は行わない
- あくまで「最初に置いた石」による1回の操作のみ
操作後の盤面を上から順に H 行出力する
入力例:
3 3 0 0
..*
...
***
出力例:
***
*..
***
✅OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const [H, W, Y, X] = lines[0].split(' ').map(Number);
const grid = lines.slice(1).map(line => line.split(''));
let startRow = Infinity;
let startCol = Infinity;
let endRow = -Infinity;
let endCol = -Infinity;
// 縦
for (let i = 0; i < H; i++) {
if (grid[i][X] === '*' && i < Y) {
startCol = i;
} else if (grid[i][X] === '*' && i > Y) {
endCol = i;
break;
}
}
for (let i = Math.min(startCol, Y); i < Math.max(endCol, Y+1); i++) {
grid[i][X] = '*'
}
// 横
for (let i = 0; i < W; i++) {
if (grid[Y][i] === '*' && i < X) {
startRow = i;
} else if (grid[Y][i] === '*' && i > X) {
endRow = i;
break;
}
}
for (let i = Math.min(startRow, X); i < Math.max(endRow, X+1); i++) {
grid[Y][i] = '*'
}
// 出力
grid.forEach(g => console.log(g.join('')));
});
🔍コードの流れ
- 入力を読み込み、
- 盤面サイズ
H,W、石を置く座標Y,X、盤面gridを作成する - 縦方向・横方向で、はさむ石の 開始位置と終了位置 を記録する変数を用意する
縦方向の処理
- 列
Xを上から下へ走査する -
(Y, X)より 上側にある一番近い'*'をstartColに記録 -
(Y, X)より 下側で最初に見つかった'*'をendColに記録して探索終了 -
startCol〜endColの範囲をすべて'*'にする
横方向の処理
- 行
Yを左から右へ走査する -
(Y, X)より 左側にある一番近い'*'をstartRowに記録 -
(Y, X)より 右側で最初に見つかった'*'をendRowに記録して探索終了 -
startRow〜endRowの範囲をすべて'*'にする
出力
- 更新後の盤面を 1 行ずつ出力する
✨OK例:
const rl = require('readline').createInterface({ input: process.stdin});
const lines = [];
rl.on('line', line => lines.push(line));
rl.on('close', () => {
const [H, W, Y, X] = lines[0].split(' ').map(Number);
const grid = lines.slice(1).map(row => row.split(''));
// まず石を置く
grid[Y][X] = '*';
// 4方向(上・下・左・右)
const directions = [
[-1, 0], // 上
[1, 0], // 下
[0, -1], // 左
[0, 1], // 右
];
for (const [dy, dx] of directions) {
let ny = Y + dy;
let nx = X + dx;
const path = []; // この方向で通過したマス
// 盤面内を進む
while (0 <= ny && ny < H && 0 <= nx && nx < W) {
if (grid[ny][nx] === '*') {
// はさめた → 間をすべて '*'
for (const [py, px] of path) {
grid[py][px] = '*';
}
break;
}
// 通過マスを保存
path.push([ny, nx]);
// 次のマスへ
ny += dy;
nx += dx;
}
}
// 出力
console.log(grid.map(row => row.join('')).join('\n'));
});
🔍 コードの流れ
- 入力をすべて読み込み、
盤面サイズH,Wと石を置く座標Y,X、盤面gridを作成する - 指定されたマス (
Y,X) に石'*'を置く - 上・下・左・右の 4方向を順番に調べる
- 各方向について
- (
Y,X) の隣のマスから探索を開始する - 探索中に通ったマスを
pathに記録する
- (
- 探索中に
- 盤面の外に出たら → その方向は何もしない
-
'*'が見つかったら →pathに記録したマスをすべて'*'にする(はさみうち成立)
- 4方向すべての処理が終わったら、最終的な盤面を出力する
📝まとめ
- 二次元配列で盤面を管理すると処理しやすい
- 処理は 4方向(上・下・左・右)を独立して考える
- 各方向でやることは同じ
- 1マスずつ進む
- 通過したマスを一時的に保存する
- 途中で
-
'*'が見つかった → はさみ成功
→ 保存したマスをすべて'*'にする - 盤面の外に出た → 失敗(何もしない)
-