본문 바로가기
백준/골드

[node.js,백준]2580 - 스도쿠

by goodchuck 2024. 5. 21.

목차

     문제

    링크

    https://www.acmicpc.net/problem/2580

    문제

    스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다.

     

    나머지 빈 칸을 채우는 방식은 다음과 같다.

    1. 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
    2. 굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.

    위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.

     

    또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈 칸에는 3이 들어가야 한다.

     

    이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.

     

    게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈 칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.

    입력

    아홉 줄에 걸쳐 한 줄에 9개씩 게임 시작 전 스도쿠판 각 줄에 쓰여 있는 숫자가 한 칸씩 띄워서 차례로 주어진다. 스도쿠 판의 빈 칸의 경우에는 0이 주어진다. 스도쿠 판을 규칙대로 채울 수 없는 경우의 입력은 주어지지 않는다.

     

    출력

    모든 빈 칸이 채워진 스도쿠 판의 최종 모습을 아홉 줄에 걸쳐 한 줄에 9개씩 한 칸씩 띄워서 출력한다.

    스도쿠 판을 채우는 방법이 여럿인 경우는 그 중 하나만을 출력한다.

     

    주저리

    백트래킹으로 푸는 문제로 재귀함수를 사용하였다.

    go라는 재귀함수를사용해서 처음에 스도쿠에있는 0의 갯수를 세어준후 재귀를 반복하면서 0의 갯수만큼 index가 되면 정답을 찾은 것이된다. 스도쿠는 row와 col 그리고 박스 에서 숫자를 찾아야하기때문에 usedRow, Col, Nine을 이용해서 세 함수에서 다 사용하지 않은 수는 go를 반복해서 이어준다.

    그리고 check함수를 통과 못하는 경우가있는데 그럴땐 return을해서 그 부분은 재귀를 종료시켜준다.

    그리고 스도쿠 완성된 판을 저장해야하는데 저때 일반적으로 저장하면 해당 값이 다돌아온 원래값이 참조되어 저장되기때문에 완성했을 때 deepcopy로 저장해주는것도 중요하였다.

     

    풀이

     checkZeroNums  함수

    • 퍼즐 내에서 채워야할 빈 칸의 갯수를 셉니다.

     유효성 검사 함수(usedRow, usedCol, usedNine)

    • usedRow(y, num) : y번째 행에 num이 있는지 검사합니다.
    • usedCol(x, num) : x번째 열에 num이 있는지 검사합니다.
    • usedNine(x, y, num) : (x, y)가 속한 3x3 박스에 num이 있는지 검사합니다.

     백트래킹 함수

     

    • check(index, x, y): 주어진 위치에 숫자를 채우고 유효성을 검사한 후, 다음 빈 칸을 찾습니다.
    • go(index, x, y): 퍼즐을 해결하기 위한 백트래킹 함수입니다. 모든 빈 칸이 채워지면 결과를 저장하고 종료합니다.

     solution 함수

    • solution(): 백트래킹 함수를 호출하여 퍼즐을 해결하고 결과를 출력합니다.

     

     코드

    // 스도쿠
    var fs = require('fs');
    var input = fs.readFileSync('/dev/stdin').toString();
    
    let real = input.trim().split("\n");
    let maze = real;
    maze = maze.map(v => [...v.split(" ")].map(Number));
    
    let width = maze[0].length;
    let height = maze.length;
    
    
    function checkZeroNums(maze) {
        let zeroCount = 0;
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                if (maze[i][j] === 0) {
                    zeroCount++;
                }
            }
        }
        return zeroCount;
    }
    let count = checkZeroNums(maze);
    
    function usedRow(y, num) {
        for (let i = 0; i < 9; i++) {
            if (maze[y][i] === num) {
                return true;
            }
        }
        return false;
    }
    function usedCol(x, num) {
        for (let i = 0; i < 9; i++) {
            if (maze[i][x] === num) {
                return true;
            }
        }
        return false;
    }
    function usedNine(x, y, num) {
        // 9칸 확인
        // 
        let nx = x % 9
        if (nx >= 0 && nx <= 2) {
            nx = 0;
        }
        else if (nx >= 3 && nx <= 5) {
            nx = 3;
        }
        else if (nx >= 6 && nx <= 8) {
            nx = 6;
        }
        let ny = y % 9
        if (ny >= 0 && ny <= 2) {
            ny = 0;
        }
        else if (ny >= 3 && ny <= 5) {
            ny = 3;
        }
        else if (ny >= 6 && ny <= 8) {
            ny = 6;
        }
        for (let i = ny; i < ny + 3; i++) {
            for (let j = nx; j < nx + 3; j++) {
                // console.log({ i, j }, maze[i][j]);
                if (maze[i][j] === num) {
                    return true;
                }
            }
        }
        return false;
    }
    
    function check(index, x, y) {
        for (let num = 1; num <= 9; num++) {
            let findW = usedRow(y, num);
            let findH = usedCol(x, num);
            let findN = usedNine(x, y, num);
            if (!findW && !findH && !findN) {
                maze[y][x] = num;
                go(index + 1, x, y)
                maze[y][x] = 0;
            }
        }
        return false;
    }
    let returns = [];
    function go(index, x, y) {
        if (index === count) {
            // console.log("무한루프 확인", index, maze)
            if (returns.length === 0) {
                returns.push(JSON.parse(JSON.stringify(maze)));
                return false;
            }
            return true;
        }
    
        for (let i = y; i < height; i++) {
            for (let j = 0; j < width; j++) {
                if (maze[i][j] === 0) {
                    let num = check(index, j, i);
                    if (!num) {
                        // console.log("못푸는 퍼즐")
                        return false;
                    }
                }
            }
        }
    }
    
    
    async function solution() {
        // console.log(maze)
        go(0, 0, 0);
        let answer = [];
        returns[0].forEach(row => answer.push(row.join(" ")));
        console.log(answer.join("\n"));
    }
    solution();

    '백준 > 골드' 카테고리의 다른 글

    [node.js, 백준]16197 - 두 동전  (0) 2024.06.07
    [node.js, 백준]9663 - N-Queen  (0) 2024.05.21
    [node.js,백준]1987 - 알파벳  (0) 2024.05.16
    [node.js,백준]13398 - 연속합 2  (0) 2024.05.11
    [node.js,백준]2133 - 타일 채우기  (0) 2024.05.10