문제 출처: https://www.acmicpc.net/problem/7576
문제

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토의 인접한 곳은 왼쪽, 오른쪽, 앞, 뒤 네 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지, 그 최소 일수를 알고 싶어 한다.
토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
    private static int[][] directions = {
    		{ -1, 0 },	// 상
    		{ 1, 0 },	// 하
    		{ 0, -1 },	// 좌
    		{ 0, 1 },	// 우
    };
    
    private static Queue<int[]> tomatoList = new ArrayDeque<>(); // 인덱스 0: 행, 인덱스 1: 열
    private static int[][] tomatoes; // 토마토 정보를 저장하기 위한 배열
	private static int countOne, countZero, countMinusOne; // 1, 0, -1 각각 카운트
    private static int result; // 정답용
	public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
        
        int M = Integer.parseInt(tokenizer.nextToken()); // 상자 가로 길이
        int N = Integer.parseInt(tokenizer.nextToken()); // 상자 세로 길이
        
        tomatoes = new int[N][M];
        for (int i = 0; i < N; i++) {
			tokenizer = new StringTokenizer(reader.readLine());
			for (int j = 0; j < M; j++) {
				tomatoes[i][j] = Integer.parseInt(tokenizer.nextToken());
				
				if (tomatoes[i][j] == 1) {
					tomatoList.offer(new int[] { i, j }); // 익은 토마토 위치 삽입
					countOne++;
				}
				else if (tomatoes[i][j] == 0) countZero++;
				else countMinusOne++;
			}
		}
        
        // 모든 토마토가 다 익었거나, 익은 토마토와  빈 상자로만 구성된 경우 바로 리턴
        if (countOne == N * M || countOne + countMinusOne == N * M) {
        	System.out.println(0);
        	return;
        }
        
        while (!tomatoList.isEmpty()) {
        	int size = tomatoList.size();
			boolean flag = false;
        	while (size-- > 0) {
				if (bfs(tomatoList.poll(), N, M)) flag = true;
			}
        	if (flag) result++;
		}
        // 모두 익지 못하는 상황
        if (countOne + countMinusOne != N * M) {
			System.out.println(-1);
			return;
		}
		System.out.println(result);
    }
    
    private static boolean bfs(int[] code, int N, int M) {
    	int y = code[0];
    	int x = code[1];
    	boolean flag = false;
		for (int i = 0; i < 4; i++) {
			int dy = y + directions[i][0];
			int dx = x + directions[i][1];
			// 유효한 좌표값이고
			if (dy >= 0 && dy < N && dx >= 0 && dx < M) {
				// 익지 않은 토마토일 경우
				if (tomatoes[dy][dx] == 0) {
					tomatoes[dy][dx] = 1; // 토마토가 익음
					countOne++;
					flag = true;
					tomatoList.add(new int[] { dy, dx });
				}
			}
		}
		return flag;
    }
}