二次元配列の要素を時計回りの螺旋順で収集するアルゴリズムを解説します。主なアプローチとして、境界値を逐次更新する方法と、方向ベクトルと訪問済みフラグを利用する方法の2つを紹介します。
境界縮小方式(推奨)
この手法では、未処理領域の上下左右の境界を保持し、外周を一周するたびに境界を内側に縮めます。各周回で4つの辺(上辺→右辺→下辺→左辺)を順に走査し、境界を更新します。終了条件は「上境界が下境界を超える」または「左境界が右境界を超える」です。
import java.util.*;
class SpiralTraverse {
public List<Integer> traverse(int[][] grid) {
List<Integer> result = new ArrayList<>();
if (grid == null || grid.length == 0 || grid[0].length == 0) return result;
int rows = grid.length, cols = grid[0].length;
int up = 0, down = rows - 1, leftEdge = 0, rightEdge = cols - 1;
while (up <= down && leftEdge <= rightEdge) {
for (int col = leftEdge; col <= rightEdge; col++)
result.add(grid[up][col]);
up++;
if (up > down) break;
for (int row = up; row <= down; row++)
result.add(grid[row][rightEdge]);
rightEdge--;
if (leftEdge > rightEdge) break;
for (int col = rightEdge; col >= leftEdge; col--)
result.add(grid[down][col]);
down--;
if (up > down) break;
for (int row = down; row >= up; row--)
result.add(grid[row][leftEdge]);
leftEdge++;
}
return result;
}
}
方向ベクトル+訪問管理方式
方向ベクトル配列 {右(0,1), 下(1,0), 左(0,-1), 上(-1,0)} を用意し、現在位置から次のマスへ移動します。移動先が範囲外または既訪問であれば、方向を90度右回転させます。全要素数分のステップを繰り返すことで螺旋走査を実現します。
import java.util.*;
class DirectionalTraverse {
public List<Integer> traverse(int[][] grid) {
List<Integer> result = new ArrayList<>();
if (grid == null || grid.length == 0 || grid[0].length == 0) return result;
int total = grid.length * grid[0].length;
boolean[][] visited = new boolean[grid.length][grid[0].length];
int[] dy = {0, 1, 0, -1}, dx = {1, 0, -1, 0};
int y = 0, x = 0, dir = 0;
for (int i = 0; i < total; i++) {
result.add(grid[y][x]);
visited[y][x] = true;
int ny = y + dy[dir], nx = x + dx[dir];
if (ny < 0 || ny >= grid.length || nx < 0 || nx >= grid[0].length || visited[ny][nx]) {
dir = (dir + 1) % 4;
ny = y + dy[dir];
nx = x + dx[dir];
}
y = ny;
x = nx;
}
return result;
}
}
比較と選択指針
| 手法 | 時間計算量 | 空間計算量 | 特徴 |
|---|---|---|---|
| 境界縮小 | O(m×n) | O(1) | メモリ効率が良く、実装も直感的。面接や実務に最適。 |
| 方向ベクトル | O(m×n) | O(m×n) | 汎用性が高く、他の巡回パターンへの応用が容易。 |