4056번: 스-스-스도쿠

바다에서 수학을 좋아하기로 유명한 오징어의 취미는 스도쿠이다. 스도쿠는 9*9칸으로 이루어져 있으며, 3*3칸에 1~9까지가 1번씩, 각각의 가로줄에도 1번씩, 세로줄에도 1번씩 들어가게 만드는 게

www.acmicpc.net

 - 문제 설명 - 

 

스-스-스도쿠 문제는  백트래킹을 활용하여 스도쿠를 풀어내는 문제입니다. 

 

백준 2580번 문제와 비슷하지만, 스도쿠가 만들어질 수 있는 경우만 만들어지는 2580번 스도쿠 문제와는 다르게 스-스-스도쿠는 스도쿠를 만들지 못하는 경우도 생깁니다.

 

스도쿠를 만드는 백트래킹 코드를 먼저 작성하고, 스도쿠를 만들지 못 할 경우를 조건으로 추가로 달아주면 되는 문제였습니다.

 

 

 복습할 문제 선정 이유 :

  • 스도쿠를 만들지 못 할 경우를 체크했다고 생각하여 계속 틀린 답을 선택하게 되었습니다. 
  • 처음에는 스도쿠를 푸는 로직을 백트래킹으로 수월하게 푸는 방법을 복습하려고 하였으나, 틀린 경우를 생각하는 습관도 기르고자 복습할 문제로 선정하였습니다.

** 코드는 더보기 칸을 눌러서 확인해주세요 **

 

1. 스도쿠를 풀어내는 로직 만들기

  • 스도쿠의 빈 칸을 채워넣기 위해선 그 자리의 행과, 열과, 작은 사각형에 해당 칸의 값이 들어가지 않는 경우에 넣을 수 있습니다. 행, 열, 작은 사각형의 숫자들을 체크할 리스트를 만듭니다.
더보기
# 1. 9개 행의 숫자 입력받기
    sudoku = [list(map(int, input())) for _ in range(9)]
# 스도쿠에서 체크해야할 세가지
    r_check = [[False] * 10 for _ in range(9)]  # 가로 안에 들어갈 수 있는 숫자 체크하기 0~8번줄에 1~9까지의 숫자
    c_check = [[False] * 10 for _ in range(9)]  # 세로 안에 들어갈 수 있는 숫자 체크하기
    s_check = [[False] * 10 for _ in range(9)]  # 작은 사각형 안에 들어갈 수 있는 숫자 체크하기

 

  • 스도쿠를 한 바퀴 돌면서 0이 아닌 숫자가 있는 경우 해당 칸의 행, 열, 작은 사각형에 숫자가 있다고 체크를 해줍니다. 이왕 한 번 반복하는김에 0인 경우도 나중에 백트래킹 처리를 위해 스택에 값을 넣어줍니다.
더보기
stack = []
# 스도쿠 돌면서 체크리스트 채워주기
    for i in range(9):
        for j in range(9):
            val = sudoku[i][j]
            if val != 0 :
                    r_check[i][val] = True  # i행 숫자 체크
                    c_check[j][val] = True  # j열 숫자도 체크
                    # 작은 사각형은 i//3*3 + j//3 번째 사각형이 된다
                    s_check[i // 3 * 3 + j // 3][val] = True
            else : # 0이 아닌 경우 백트래킹을 돌려야하므로 스택에 넣어준다
                stack.append((i, j))

 

  • 스도쿠의 0인 칸들을 돌면서 백트래킹 실행 만약 True값 하나 찾았으면 다른 값들을 찾을 필요가 없는 backtracking을 만듭니다.
  • 0이 존재하는 칸에 1~9를 넣어봅니다. 이 때 1~9까지 가능한 지 체크하기 위해서 이전에 만들어두었던 값들을 활용합니다.
  • 스도쿠를 완성하지 못해 되돌아 왔을 경우, 넣어봤던 수들의 행, 열, 작은사각형 체크들은 다시 False로 바꿔주어야 한다는 것을 잊지 않습니다.
더보기
def backtracking(level):
    # 1. 종료조건
    if level == len(stack):
        return True
    else:
        for j in range(1, 10):  # 1~9까지의 숫자 넣어보기
            r, c = stack[level]  # r번째행 c번째 열에 있는 0 채워주기
            if not r_check[r][j] and not c_check[c][j] and not s_check[r//3*3 + c//3][j]:
                sudoku[r][c] = j  # 0 채워넣고
                r_check[r][j] = True  # 모든 행과 열과 작은사각형 방문처리 해준 뒤
                c_check[c][j] = True
                s_check[r // 3 * 3 + c // 3][j] = True
                if backtracking(level + 1):  # 다음 0 찾기(만약 찾아서 True 를 반환했으면 return True
                    return True
                else:  # 만약 다음 0을 못 찾고 돌아왔으면 다음 값을 넣어야하므로 모두 초기화
                    sudoku[r][c] = 0
                    r_check[r][j] = False
                    c_check[c][j] = False
                    s_check[r // 3 * 3 + c // 3][j] = False
    return False

 

 

 

2. 스도쿠가 가능하지 않은 경우 체크해주기

  • 스도쿠가 불가능한 경우는 backtracking에서 True값을 반환하지 못하는 경우입니다. 
if backtracking(0) :
        for i in sudoku :
            print("".join(map(str, i)))
else :
    print("Could not complete this grid.")
print()
  • 이렇게 처리해주고 다 풀었다~ 생각하면서 제출을 하면 틀렸습니다! 라는 문구를 볼 수 있어요.

  • 스도쿠가 불가능 한 경우는 이렇게 넣을 수 있는 숫자가 없는 경우도 있지만, 문제 자체가 잘못 됐을 경우에도 스도쿠를 만들 수 없다는 것을 간과했습니다.
  • 주어진 스도쿠 문제를 반복문으로 돌릴 때, 해당 열, 행, 작은 사각형에 숫자를 체크해주고, True로 바꿔주었습니다. 백트래킹을 할 때와 마찬가지로 처음에 보드에 스도쿠 문제로 주어진 숫자를 채워줄 때도, 숫자가 들어갈 수 있는지 체크를 해주어야합니다.
  • 스도쿠 문제를 만들기 전에 조건을 걸어두어 불가능 한 경우 check 값을 추가해줍니다.
더보기
if not r_check[i][val] or not c_check[j][val] or not s_check[i // 3 * 3 + j // 3][val] :
                check = False
                break

 

 

최종 코드 

 

더보기
def backtracking(level):
    # 1. 종료조건
    if level == len(stack):
        return True
    else:
        for j in range(1, 10):  # 1~9까지의 숫자 넣어보기
            r, c = stack[level]  # r번째행 c번째 열에 있는 0 채워주기
            if not r_check[r][j] and not c_check[c][j] and not s_check[r//3*3 + c//3][j]:
                sudoku[r][c] = j  # 0 채워넣고
                r_check[r][j] = True  # 모든 행과 열과 작은사각형 방문처리 해준 뒤
                c_check[c][j] = True
                s_check[r // 3 * 3 + c // 3][j] = True
                if backtracking(level + 1):  # 다음 0 찾기(만약 찾아서 True 를 반환했으면 return True
                    return True
                else:  # 만약 다음 0을 못 찾고 돌아왔으면 다음 값을 넣어야하므로 모두 초기화
                    sudoku[r][c] = 0
                    r_check[r][j] = False
                    c_check[c][j] = False
                    s_check[r // 3 * 3 + c // 3][j] = False
    return False


T = int(input())

for tc in range(1, T + 1):
    # 1. 9개 행의 숫자 입력받기
    sudoku = [list(map(int, input())) for _ in range(9)]

    # 스도쿠에서 체크해야할 세가지
    r_check = [[False] * 10 for _ in range(9)]  # 가로 안에 들어갈 수 있는 숫자 체크하기 0~8번줄에 1~9까지의 숫자
    c_check = [[False] * 10 for _ in range(9)]  # 세로 안에 들어갈 수 있는 숫자 체크하기
    s_check = [[False] * 10 for _ in range(9)]  # 작은 사각형 안에 들어갈 수 있는 숫자 체크하기
    # 스도쿠에서 0인 곳 체크하기
    stack = []
    check = True
    # 스도쿠 돌면서 체크리스트 채워주기
    for i in range(9):
        for j in range(9):
            val = sudoku[i][j]
            if r_check[i][val] == True or c_check[j][val] == True or s_check[i // 3 * 3 + j // 3][val] == True :
                check = False
                break
            if val != 0 :
                r_check[i][val] = True  # i행 숫자 체크
                c_check[j][val] = True  # j열 숫자도 체크
                # 작은 사각형은 i//3*3 + j//3 번째 사각형이 된다
                s_check[i // 3 * 3 + j // 3][val] = True
            else:  # 0이면 스택에 넣어주기
                stack.append((i, j))
    if backtracking(0) and check :
        for i in sudoku :
            print("".join(map(str, i)))
    else :
        print("Could not complete this grid.")
    print()

 

+ Recent posts