개발
백준

문제 탐색하기
1) 문제 분석
5*5 빙고판, 25개의 숫자
세 개의 선이 그어지면, 빙고
사회자가 몇 번째 수를 부르면 철수가 빙고를 외치는지 출력
2) 가능한 시간복잡도
(1) 입력 처리
총 10줄의 입력을 받아 input.map(line => line.split(' ').map(Number))로 각 숫자를 파싱하여 2차원 배열(infos)을 생성한다.
각 줄은 고정된 5개 숫자이며, 10줄이므로 총 50개의 숫자를 처리.
→ O(50) ≈ O(1) (고정 크기이므로 상수 시간)
(2) 빙고판 숫자 위치 매핑
5x5 빙고판의 각 숫자에 대해 위치를 기록한다.
이중 배열을 순회하므로 → O(N의 제곱)
N = 5이므로 O(25) ≈ O(1) (실제로 매우 작음)
(3) 빙고 체크
가로 5줄 + 세로 5줄 확인 → 각각 O(N의 제곱)
대각선 2줄 확인 → 각각 O(N)
총합은 O(N의 제곱)
N=5 고정이므로 O(25) ≈ O(1)
(4) 빙고 게임 진행
사회자가 부르는 숫자는 총 25개.
숫자 하나를 부를 때마다:
숫자 위치 확인 및 표시 → O(1)
checkBingo() 호출 → O(N의 제곱)
최악의 경우 25번 반복.
→ 25 * O(N의 제곱) = O(N의 제곱) (상수 배수는 무시)
(5) 전체 시간복잡도
입력 처리 : O(1)
숫자 위치 매핑 : O(N의 제곱)
빙고 게임 진행 : O(N의 제곱)-> 총합 : O(N의 제곱)
3) 알고리즘 선택 이유
이 문제는 빙고판 상태를 실시간으로 관리하면서 선이 몇 개인지를 빠르게 확인해야 한다. 숫자 하나가 불릴 때마다 빙고 여부를 검사해야 하므로, 모든 줄(가로, 세로, 대각선)에 대해 즉각적인 판별이 필요한 문제이다.
따라서:
빙고판 자체는 이중 배열로 관리하고,
숫자마다 직접 탐색을 통해 빙고 여부를 판단하는 완전탐색(브루트포스) 접근이 자연스럽고 최적이다.
입력 크기(N=5)가 작고 판의 구조도 단순하기 때문에 O(N²) 알고리즘을 사용해도 전혀 문제가 되지 않는다.
-> 즉, 이 문제는 완전탐색(브루트포스) 기반으로 해결하는 것이 가장 직관적이고 효율적인 선택이다.
잠깐! 브루트포스에 대한 간단한 설명, 그리고 관련 예시 문제는 여기를 참고하자.
코드 설계하기
1. 실행 구조
1) 사용자의 빙고판과 사회자가 부르는 숫자 입력
입력받은 데이터를 공백 기준으로 나눈 후 숫자로 변환하여 두 개의 배열로 분리한다.
하나는 철수의 빙고판, 다른 하나는 사회자가 부르는 숫자 목록으로 저장한다.
2) 빙고판 상태 초기화
빙고판의 각 칸이 지워졌는지 여부를 관리하기 위해, false로 채워진 5x5 이중 배열을 선언한다.
각 숫자의 위치를 빠르게 찾을 수 있도록, 숫자와 해당 좌표를 매핑한 객체를 생성한다.
3) 빙고 체크 함수 구현
가로, 세로, 대각선에 대해 빙고 여부를 검사하는 함수를 작성한다.
5개의 칸이 모두 체크(true)되었을 경우 선의 개수를 증가시켜 반환한다.
4) 빙고 게임 진행
사회자가 숫자를 부를 때마다 해당 숫자의 좌표를 찾아 빙고판 상태 배열에서 해당 칸을 true로 표시한다.
숫자를 부를 때마다 빙고 체크 함수를 호출해 선 개수를 확인한다.
선이 3개 이상 만들어지는 순간, 몇 번째 숫자를 부른 직후인지 출력하고 프로그램을 종료한다.
2. 고민이 되었던 부분
: 철수가 “빙고”를 외칠 수 있으려면 어떤 로직을 구현해야 할지 가장 먼저 고민하게 되었다. “3개의 선이 완성되면 빙고”라는 조건을 충족하기 위한 방법을 찾는 것이 핵심 과제였다. 이를 해결하기 위해 두 가지 전제를 세웠다.
첫 번째, 각 숫자가 지워졌는지를 확인할 수 있는 이중 배열 형태의 빙고판 상태를 따로 관리해야 했다. 또한 빠르게 숫자의 좌표를 찾기 위해 숫자와 좌표를 매핑한 객체를 함께 선언하여, 숫자가 불릴 때마다 해당 좌표를 즉시 찾을 수 있도록 설계했다.
두 번째, 빙고가 발생했는지를 확인할 빙고 체크 함수를 선언했다. 이 함수에서는:
가로, 세로, 대각선을 모두 검사하며,
각각의 선이 모두 지워졌다면 선 개수를 증가시켰다.
선의 총 개수가 3개 이상일 때 빙고임을 판별할 수 있도록 구성했다.
이렇게 함으로써, 숫자가 하나씩 불릴 때마다 실시간으로 빙고 여부를 확인하고,
조건이 만족되는 순간 즉시 종료할 수 있는 구조를 완성할 수 있었다.
정답 코드
// 사용 언어 : Javascript
// 입력 처리
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin').toString().trim().split('\n');
const infos = input.map(line => line.split(' ').map(Number)); // 숫자 배열로 변환
const bingoBoard = infos.slice(0, 5); // 빙고판
const calledNumbers = infos.slice(5); // 사회자가 부르는 숫자
// 빙고판 초기화
const bingo = Array.from({ length: 5 }, () => Array(5).fill(false));
// 빙고판 숫자 위치 저장
const numberPosition = {};
bingoBoard.forEach((row, i) => {
row.forEach((num, j) => {
numberPosition[num] = [i, j]; // 숫자와 위치를 매핑
});
});
// 빙고 체크 함수
function checkBingo() {
let count = 0;
// 가로 체크
for (let i = 0; i < 5; i++) {
if (bingo[i].every(cell => cell)) {
count++;
}
}
// 세로 체크
for (let j = 0; j < 5; j++) {
if (bingo.every(row => row[j])) {
count++;
}
}
// 대각선 체크
if (bingo.every((row, idx) => row[idx])) {
count++;
}
if (bingo.every((row, idx) => row[4 - idx])) {
count++;
}
return count;
}
// 빙고 횟수 변수 초기화
let bingoCount = 0;
// 빙고 게임 진행
for (let row of calledNumbers) {
for (let num of row) {
bingoCount++; // 숫자 부른 횟수 증가
const [x, y] = numberPosition[num];
bingo[x][y] = true;
if (checkBingo() >= 3) {
console.log(bingoCount);
process.exit(0); // 빙고가 되면 프로그램 종료
}
}
}마무리하며
처음 이 문제를 접했을 때 단순히 빙고판에서 숫자를 지우는 과정만 생각했지만, 조금만 더 들여다보니 ‘3줄이 완성될 때 빙고를 외친다’는 조건이 생각보다 중요한 핵심임을 깨달았다. 숫자를 지우는 것 자체는 단순했지만, 가로, 세로, 대각선에서 선이 언제 몇 개가 만들어지는지를 관리하는 것이 진짜 문제 해결의 본질이라는 점을 뒤늦게 알게 되었다.
개인적으로 이번 문제를 풀면서, 문제의 본질을 파악하는 힘이 얼마나 중요한지 다시 한 번 느꼈다. 코드로 구현하는 것은 결국 내가 문제를 어떻게 이해했는지의 반영이기 때문에, 문제를 단순히 ‘코드로 푼다’는 생각보다는, 상황을 정확히 이해하고 그 구조를 코드로 옮기는 과정임을 더 체감하게 되었다.
이번 문제를 통해 새삼 깨달은 건, 정답을 맞추는 것이 끝이 아니라는 점이었다. 문제를 이해하고 나만의 방법으로 풀어내는 과정 자체가 중요했고, 무엇보다 더 나은 방향을 고민하는 습관이 문제 해결력의 일부라는 걸 경험할 수 있었다. 앞으로도 단순히 빠르게 푸는 것이 아니라, 내가 구현하는 코드가 ‘좋은 코드’인지 계속 고민하고 점검하는 개발자가 되고 싶다.