今回は paiza の「【マップの扱い 4】マップのナンバリング」の問題に挑戦!
マップ問題の最終問題で、今までのより解きごたえがあった!
問題概要
〇 与えられるもの
- マップの 行数
H
と 列数W
- ナンバリングの方向
D
(1~4の整数)
〇 目的
- マップの左上 (0,0) から順に番号を振る。
- 番号の振り方は
D
の値によって異なる方向。 - 出力として
H
×W
の番号付きマップ を出力する。
〇 座標
- 左上が (0,0)
- 下方向が
y
の正方向、右方向がx
の正方向
〇 ナンバリング方向(D
の定義)
-
D = 1
→ 右上 ↗- 斜めに下から上へ番号を振る
-
D = 2
→ 右 →- 行ごとに左から右へ番号を振る
-
D = 3
→ 下 ↓- 列ごとに上から下へ番号を振る
-
D = 4
→ 左下 ↙- 斜めに上から下へ番号を振る
入力例:
4 4 1
出力例:
1 3 6 10
2 5 9 13
4 8 12 15
7 11 14 16
↓ 図があった方がわかりやすいので、paiza のサイトを見てみて!
✅ OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const [H, W, D] = lines[0].split(' ').map(Number);
// 2次元配列を用意
const map = Array.from({ length: H }, () => Array(W).fill(0));
let num = 1;
if (D === 2) {
// 横方向 →
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
map[y][x] = num++;
}
}
} else if (D === 3) {
// 縦方向 ↓
for (let x = 0; x < W; x++) {
for (let y = 0; y < H; y++) {
map[y][x] = num++;
}
}
} else if (D === 1) {
// 斜め方向 右上 ↗
for (let s = 0; s <= H + W - 2; s++) {
for (let y = H - 1; y >= 0; y--) {
let x = s - y;
if (0 <= x && x < W) {
map[y][x] = num++;
}
}
}
} else if (D === 4) {
// 斜め方向 左下 ↙
for (let s = 0; s <= H + W - 2; s++) {
for (let y = 0; y < H; y++) {
let x = s - y;
if (0 <= x && x < W) {
map[y][x] = num++;
}
}
}
}
// 出力
map.forEach(row => console.log(row.join(' ')));
});
〇 2次元配列の準備
-
H
×W
のマップを0
で初期化
〇 番号振り用のカウンタ
-
num = 1
からスタート
〇 D
に応じた番号振り分け
-
D=2
(右 →):行ごとに左から右へ -
D=3
(下 ↓):列ごとに上から下へ -
D=1
(右上 ↗):斜めのラインごとに下から上へ-
y + x = s
のマスを1つのグループとして処理
-
-
D=4
(左下 ↙):斜めのラインごとに上から下へ- 同じく
y + x = s
のマスを処理
- 同じく
〇 出力
- 2次元配列を行ごとにスペース区切りで表示
🔹 1. D = 2(右 →)
普通の「横書き」。
左上 (0,0) から始めて、右に進む。行の右端まで行ったら次の行の左端へ。
例: H=3, W=4
1 2 3 4
5 6 7 8
9 10 11 12
コードの対応部分:
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
grid[y][x] = num++;
}
}
🔹 2. D = 3(下 ↓)
「縦書き」。
左上 (0,0) から始めて、下に進む。列の下端まで行ったら、次の列の上端へ。
例: H=3, W=4
1 4 7 10
2 5 8 11
3 6 9 12
コードの対応部分:
for (let x = 0; x < W; x++) {
for (let y = 0; y < H; y++) {
grid[y][x] = num++;
}
}
🔹 3. D = 1(右上 ↗)
「斜め右上方向」に番号を振る。
📌 処理は、y + x
が同じ値のマスは同じ斜め(↗方向の線)に並んでいることを使う。
そのとき、下側から上に進むのが D=1
の特徴。
📌 詳しく → 各マス(座標)に y+x
を書いてみると:(H=3, W=4)
(0,0)=0 (0,1)=1 (0,2)=2 (0,3)=3
(1,0)=1 (1,1)=2 (1,2)=3 (1,3)=4
(2,0)=2 (2,1)=3 (2,2)=4 (2,3)=5
グループごとに見ると(s = y + x
):
s=0 → {(0,0)}
s=1 → {(0,1), (1,0)}
s=2 → {(0,2), (1,1), (2,0)}
s=3 → {(0,3), (1,2), (2,1)}
s=4 → {(1,3), (2,2)}
s=5 → {(2,3)}
これが「斜めのグループ分け」になっている。
例: H=3, W=4
1 3 6 10
2 5 9 11
4 8 12 13
コードの対応部分:
for (let s = 0; s <= H + W - 2; s++) {
for (let y = H - 1; y >= 0; y--) { // 下から上へ
let x = s - y;
if (0 <= x && x < W) {
grid[y][x] = num++;
}
}
}
① for (let s = 0; s <= H + W - 2; s++)
-
s = y + x
の「斜めのグループ番号」を表している。 - 例えば
s=2
のとき、(2,0)
,(1,1)
,(0,2)
が候補になる。 -
s
の範囲は 最小 = 0(左上)〜最大=(H-1)+(W-1) まで。 - なので
s <= H+W-2
👉 「今どの斜めのグループを処理しているか」を表すループ
② for (let y = H - 1; y >= 0; y--)
-
D=1
は下から上に読むので、y
を大きい方から小さい方へ動かす。 - これにより (
y
,x
) の順番が 下→上になる。
👉 「グループ内の並び順」を決めている
③ let x = s - y;
- 対角線の式
s = y + x
を変形してx = s - y
。 - つまり「今の
y
に対応するx
の位置」を計算。
👉 y
を決めたら、その斜め上にある x
が自動的に決まる
④ if (0 <= x && x < W)
-
x
が範囲外になったらスキップ。 - 例えば (
y=2
,s=0
) →x=-2
になるけどこれは盤面外。
👉 盤面の範囲チェック
⑤ grid[y][x] = num++;
- 斜めの走査順に、下から上に番号を振っていく。
💡 全体の流れ(D=1 の場合)
-
s=0
→(0,0)
→1
-
s=1
→(1,0)=2
→(0,1)=3
-
s=2
→(2,0)=4
→(1,1)=5
→(0,2)=6
- … という具合に「右上 ↗」に伸びる番号付けになる。
🔹 4. D = 4(左下 ↙)
これも「斜め」だが、今度は 上から下に進む。
D=1
と同じように処理。
例: H=3, W=4
1 2 4 7
3 5 8 10
6 9 11 12
コードの対応部分:
for (let s = 0; s <= H + W - 2; s++) {
for (let y = 0; y < H; y++) { // 上から下へ
let x = s - y;
if (0 <= x && x < W) {
grid[y][x] = num++;
}
}
}
📝 まとめ
〇 マップは2次元配列で扱う
-
H
×W
の配列を用意して番号を格納する
〇 番号振りは方向 D
によってループ順序を変える
-
D=2
(右 →):行ごとに左から右 -
D=3
(下 ↓):列ごとに上から下 -
D=1
(右上 ↗):斜めのラインごとに下から上 (y + x = s
) -
D=4
(左下 ↙):斜めのラインごとに上から下 (y + x = s
)
〇 斜めの考え方
- 同じ
y + x
の値を持つマス(座標)が斜めに並んでいるを1つのグループとして扱う -
D=1
は下から上、D=4
は上から下に処理することで向きが変わる
〇 番号カウンタを使って順番に番号を振る
-
num = 1
からスタートして順に加算
〇 出力は行ごとにスペース区切りで表示