1662번: 압축

압축되지 않은 문자열 S가 주어졌을 때, 이 문자열중 어떤 부분 문자열은 K(Q)와 같이 압축 할 수 있다. K는 한자리 정수이고, Q는 0자리 이상의 문자열이다. 이 Q라는 문자열이 K번 반복된다는 뜻이

www.acmicpc.net

 - 문제 설명 -

압축은 K(Q)은 Q * K를 의미합니다. 즉 3(5) = 555 가 됩니다.

K는 한자리 정수입니다. 그렇기 때문에 53(5) = 5가 53개 있는 것이 아니라 5 + 3(5) , 5555 가 됩니다.

압축되지 않은 문자열의 길이를 출력해야 하는데, 주의해야할 점은 압축되지 않은 문자열의 길이가 무려 21억이라는 것

 

 

복습할 문제 선정 이유 :

  • 압축되지 않은 문자열의 길이가 터무니 없게 높게 주어진 경우, 문자열을 압축해제 해서 풀어나가며 진행할 경우 시간도 오래 걸리고 메모리도 많이 사용하게 됩니다
  • 이런 특별한 경우를 처리 할 수 있는 방법을 생각해내야 하는 문제는 처음 보는 문제였기에 이 방법을 익혀두기 위해 복습할 문제로 선정하였습니다.

 

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

 

1. 압축되어있는 문자열을 압축되지 않은 상태로 되돌리기

  • 괄호 안에 있는 숫자를 여는 괄호 바로 앞에 있던 숫자만큼 곱해줍니다. 괄호 안에 다시 괄호가 나올 경우 계속 괄호 앞쪽의 숫자를 체크하는 것이 번거롭기 때문에, 뒤쪽에서부터 탐색을 해줍니다.
  • 뒤쪽에서부터 탐색을 하면 닫는 괄호가 나올때마다 스택에 담아주고 여는 괄호 다음의 숫자가 나오면 바로 바로 곱해주면서 풀어주면 되기 때문입니다.
  • 여는괄호 다음에 나오는 숫자부터 닫는괄호 전까지의 숫자까지!!
더보기
S = input()

stack = []
N = len(S)
for i in range(N-1, -1, -1) :

    stack.append(S[i]) #뒤에서부터 일단 스택에 넣기
    if i == N-1 : continue
    # ) 가 나왔을 경우 앞으로 나올 값들은 (가 나온 이후의 숫자에 곱하기를 해줄 것
    if S[i+1] == "(" : # ( 다음에 숫자가 나왔을 경우 ) 가 나올때까지의 숫자들을 현재 값에 곱하기해줄것
        s = ""
        stack.pop() # 현재 숫자가 스택에 들어가 있을테니 빼주고
        stack.pop() # 그 다음은 여는 괄호이니 다시 빼준 뒤
        while stack :
            n = stack.pop() #그 다음 숫자부터 반복문을 돌리기
            if n == ")" : # 스택에 )가 나올때까지 돌려야함
                break
            else :
                s = s + n
        s *= int(S[i]) 
        stack.append(s) # 다음 괄호를 대비해서 스택에 문자열 다시 넣어두기

 

2. 문자열을 압축된 상태로 무턱대고 되돌릴 경우 길어지는 문자열, 시간초과 or 메모리 초과의 위험성 증가

  • 우리가 필요한 건 문자열 전체가 아니라 문자열의 길이입니다.
  • 100000000000000000000000000000 이라는 문자가 있다면 우리는 이 문자열이 필요한게 아니라 길이 30 필요하다는 걸 생각해야합니다
  • 압축을 풀 때 굳이 압축이 풀린 문자열을 넣는게 아니라 문자열의 길이를 넣어주면 이 문제를 해결할 수 있습니다.
  • 여는 괄호가 나왔을 때 처리해주는 부분을 살짝 바꿔보도록 하겠습니다.
더보기
if S[i+1] == "(" :
        s = 0 # s는 이제 문자열을 받는게 아니라 문자열의 길이를 받을 것
        stack.pop() 
        stack.pop()
        while stack :
            n = stack.pop()
            if n == ")" :
                break
            else :
                if type(n) == str : # 괄호 이후 문자열이 나올 때마다 길이 1 증가
                    s += 1
                else : # 문자열이 아니라면 구해놓은 길이가 나온것이므로 그만큼 증가
                    s += n
        stack.append(s * int(S[i])) # 길이를 모두 구했으면 다음 괄호를 대비해 다시 스택에 넣어두기

 

3. 괄호가 모두 끝났다면 기존에 남아있는 문자열이 계속해서 stack에 들어갔을 것이므로 stack에 있는 값들을 한 번 정리해줍니다. (기존에 주어지는 S의 길이는 50이기 때문에 stack에 들어있는 값들을 빼주는 건 큰 문제가 없습니다)

 

 

최종 코드

S = input()

stack = []
N = len(S)

for i in range(N - 1, -1, -1):
    stack.append(S[i])
    if i == N - 1:
        continue
    if S[i + 1] == "(":
        s = 0  # s는 이제 문자열을 받는게 아니라 문자열의 길이를 받을 것
        stack.pop()
        stack.pop()
        while stack:
            n = stack.pop()
            if n == ")":
                break
            else:
                if type(n) == str:  # 괄호 이후 문자열이 나올 때마다 길이 1 증가
                    s += 1
                else:  # 문자열이 아니라면 구해놓은 길이가 나온것이므로 그만큼 증가
                    s += n
        stack.append(s * int(S[i]))  # 길이를 모두 구했으면 다음 괄호를 대비해 다시 스택에 넣어두기

ans = 0
while stack:
    n = stack.pop()
    if type(n) == int:
        ans += n
    else:
        ans += 1

print(ans)

 

 

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