Skip to content

Minjeong / 7월 5주차 / 4문제#50

Merged
zaqquum merged 4 commits intomainfrom
minjeong
Aug 5, 2024
Merged

Minjeong / 7월 5주차 / 4문제#50
zaqquum merged 4 commits intomainfrom
minjeong

Conversation

@Mingguriguri
Copy link
Collaborator

주 목표 문제 수: 3개

푼 문제


백준 #2630. 색종이 만들기: 분할정복, 재귀 / 실버2

정리한 링크: (바로가기)

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

  1. 종이의 크기 n과 정사각형의 칸의 색을 입력받아 colored_paper 리스트에 저장한다.
  2. 하얀색 색종이의 개수는 white_cnt로, 파란색 색종이의 개수는 blue_cnt로 각각 0으로 초기화한다.
  3. find_number_of_paper 함수는 색종이의 색을 분할정복하여 재귀적으로 파악하는 함수이다. x_start, y_start부터 시작하는 nxn 크기의 종이가 모두 같은 색인지 파악한다.
    1. 하얀색 종이의 경우, 모두 더하면 0이다. 따라서 범위 내에서 모두 더한 값이 0이라면 white_cnt의 값을 증가시킨다.
    2. 파란색 색종이의 경우, 1을 nn번 더한 값이다. 따라서 범위 내에서 모두 더한 값이 값이 nn이라면 blue_cnt의 값을 증가시킨다.
    3. 다른 색이 섞여있다면 종이를 4개로 분할하여 find_number_of_paper를 호출하여 동일한 작업을 시작한다.
  4. 최종적으로 하얀색 색종이(white_cnt)와 파란색 색종이(blue_cnt)의 개수를 출력한다.

🚩제출한 코드

import sys
input = sys.stdin.readline

def find_number_of_paper(x_start, y_start, n):
    global white_cnt, blue_cnt
    check_sum = 0 # 색종이가 만들어지는지 여부를 확인하기 위한 변수
    for i in range(x_start, x_start + n):
        for j in range(y_start, y_start + n):
            check_sum += colored_paper[i][j]
    if check_sum == 0:       # 하얀 색종이라면
        white_cnt += 1
    elif check_sum == n * n: # 파란 색종이라면
        blue_cnt += 1
    else: # 해당 사항 없으면 다시 분할
        find_number_of_paper(x_start, y_start, n // 2)
        find_number_of_paper(x_start + n // 2, y_start, n // 2)
        find_number_of_paper(x_start, y_start + n // 2, n // 2)
        find_number_of_paper(x_start + n // 2, y_start + n // 2, n // 2)

n = int(input())    # 한 변의 길이
colored_paper = []  # 정사각형칸의 색
for _ in range(n):
    li = list(map(int, input().split()))
    colored_paper.append(li)

white_cnt = 0   # 햐얀색 색종이의 개수
blue_cnt = 0    # 파란색 색종이의 개수

find_number_of_paper(0, 0, n)

print(white_cnt)
print(blue_cnt)

💡TIL

배운 점이 있다면 입력해주세요

  • 분할정복은 알고리즘 문제로 처음 풀어서 초기 아이디어를 떠올리는 게 쉽지 않았다. 그래서 다시 분할정복 개념도 찾아보면서 기반을 다듬는 시간이었다.
  • 분할 정복 문제를 마주하면 어떻게 접근해야 하는지 이해하게 되었다.

프로그래머스 #점프와 순간 이동: 수학 / Level 2

정리한 링크: 바로가기

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

주어진 값 계속 2로 나눠 가면서 나머지가 없으면 2로 나누고 , 나머지가 있으면 -1 (1회 점프 ; ans += 1) 해주면서 입력한 값 n이 0이 될 때까지 반복한다.

🚩제출한 코드

def solution(n):
    ans = 0
    
    while n > 0:
        if n % 2 == 0:
            n //= 2
        else:
            n -= 1
            ans += 1

    return ans

💡TIL

배운 점이 있다면 입력해주세요

  • 문제를 매우 어렵게 생각하고 있었는데 생각보다 쉬웠다! 2배씩 가고 1칸씩 간다는 점이 뭔가 홀수와 짝수 같아서 이 점에 힌트를 얻어 쭉 동작과정을 살펴보니 금방 해결할 수 있게 된 것 같다.
  • 다른 분의 풀이를 보면서 이진수 함수, bin()에 대해 알게 되었다. 쉬운 문제지만 문제 푸는 데 식견이 조금 넓어진 것 같다.

백준 #1074. Z: 분할정복, 재귀 / 실버1

정리한 링크: (바로가기)

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

  1. 2^N x 2^N 크기의 배열을 4개의 사분면으로 나눈다. 각 사분면의 크기는 2^(N-1) x 2^(N-1)이며, 순서는 왼쪽위, 오른쪽 위, 왼쪽 아래 오른쪽 아래로 동작한다.
  2. 재귀적으로 탐색하면서 (r, c) 좌표가 현재 속한 사분면을 찾는다. 그 사분면의 순서를 더해가면서 최종결과를 도출한다.
    1. 1사분면인 경우: (r, c)좌표가 해당하면 추가 계산 없이 재귀호출한다.
    2. 2사분면인 경우: 전체 배열에서 1사분면의 크기만큼 더하고 c 좌표를 반으로 줄여 다시 재귀호출한다.
    3. 3사분면의 경우: 전체 배열에서 1사분면과 2사분면 크기만큼 더하고 r 좌표를 반으로 줄여 다시 재귀호출한다.
    4. 4사분면의 경우: 전체 배열에서 1사분면, 2사분면, 3사분면의 크기만큼 더하고 c 좌표와 r좌표 모두 반으로 줄여 다시 재귀호출한다.
  3. 배열의 크기가 1 x 1이 되는 경우, (즉, N=0)탐색 순서가 0이 되고 재귀 함수를 종료한다.
  4. 재귀호출에서 반환되는 값을 순차적으로 더해가면서 최종적으로 (r, c)의 탐색순서를 반환한다.

🚩제출한 코드

def z_order(N, r, c):
    if N == 0:  # Base case: 크기가 1x1인 경우
        return 0
    half = 2 ** (N - 1)
    if r < half and c < half:  # 1사분면
        return z_order(N - 1, r, c)
    elif r < half and c >= half:  # 2사분면
        return half * half + z_order(N - 1, r, c - half)
    elif r >= half and c < half:  # 3사분면
        return 2 * half * half + z_order(N - 1, r - half, c)
    else:  # 4사분면
        return 3 * half * half + z_order(N - 1, r - half, c - half)

N, r, c = map(int, input().split())
print(z_order(N, r, c))

💡TIL

배운 점이 있다면 입력해주세요

분할(Divide)

  • 큰 문제를 작은 문제로 나누는 과정이다.
  • 이 문제에서는 2^N x 2^N 크기의 배열을 4개의 2^(N-1) x 2^(N-1) 크기의 사분면으로 나눈다.

정복(Conquer)

  • 각 작은 문제를 해결하는 단계이다.
  • 각 사분면의 크기를 줄여가면서 재귀적으로 동일한 문제를 반복해서 해결한다.

  • 나의 경우, 는 2^N x 2^N 크기의 배열을 직접 생성하고 모든 좌표를 순차적으로 방문하여 탐색 순서를 계산하는 방식으로 접근하였는데, 이렇게 접근할 경우 제한된 시간안에 답하지 못해 성능이 떨어지는 문제점이 있었다.
  • 분할정복 문제를 꼭 배열을 이용할 필요 없이 문제만 해결할 수 있으면 된다는 점을 알게 되었다.
  • 그래서 앞으로는 배열이 주어지더라도 배열을 직접 생성하는 것 대신에 필요한 좌표의 순서만 재귀적으로 계산을 하고, 언제 재귀호출을 멈춰야 하는지 명시하는 방향으로 가야겠다.

백준 #1992. 쿼드트리: 분할정복, 재귀 / 실버1

정리한 링크: 바로가기

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

처음에는 함수에 들어올 때 괄호를 열고(() 함수를 나갈 때 괄호를 닫았는데 ()) 그렇게 하니까 아래와 같이 매번 호출할 때마다 괄호가 추가되는 문제가 발생했다.

(((1)(1)(0)((0)(1)(0)(1)))((0)(0)(1)(0))(1)((0)(0)(0)(1)))

즉, 함수를 호출할 때마다 괄호가 추가하는 것이 아니라 어떤 ‘경우’에만 괄호를 추가해야 한다는 것을 알게 되었다. 즉, 압축할 수 있는 경우가 아니라 한 번 더 분할 재귀할 때 괄호를 열고, 재귀호출이 마무리 되었을 때 괄호 닫을 수 있도록 코드에 반영해야 한다.

  1. 영상의 크기 n과 영상 배열 grid를 입력받아 저장한다. 이때 영상배열은 초기에 문자열로 입력 받은 후, 각각의 문자열을 int로 변환하여 리스트로 저장한다. 또한 정답으로 출력시킬 압축된 문자열을 answer에 저장하기 위해 answer를 빈 문자열로 초기화한다.
  2. quad_tree 함수를 정의한다.
    1. 압축이 가능한지 여부를 확인하기 위한 변수인 cnt를 0으로 설정한다.
    2. grid 배열을 초기에 주어진 x 시작 위치(x_start)부터 배열의 크기 n까지, y 시작 위치(y_start)부터 배열의 크기 n까지 순회하면서 grid 요소의 값을 누적하여 cnt에 저장한다.
    3. cnt의 값이 0이라면 “0”으로 압축이 가능하며, 1nn이라면 “1”로 압축이 가능하다. 이를 answer 문자열에 추가한다.
    4. 0과 1이 섞여있다면 왼쪽 위, 오른쪽 위, 왼쪽 아래, 오른쪽 아래 4개로 분할하여 재귀호출한다. 이때 호출을 시작할 때 괄호를 열고(() 호출이 모두 끝날 때에 괄호를 닫고 ()) answer 문자열에 추가한다.
  3. 최종적으로 압축된 문자열인 answer를 출력한다.

🚩제출한 코드

import sys
input = sys.stdin.readline

def quad_tree(x_start, y_start, n):
    global answer

    cnt = 0  # 압축이 가능한 지 여부를 확인하기 위한 변수
    for i in range(x_start, x_start + n):
        for j in range(y_start, y_start + n):
            cnt += grid[i][j]
    if cnt == 0: # 다 더한 값이 0이라면 0으로 압축 가능
        answer += "0"
    elif cnt == n * n: # 다 더한 값이 1*n*n이라면 1로 압축 가능
        answer += "1"
    else: # 해당 사항 없으면 분할
		    
        answer += "("# 가장 먼저 괄호 열고
        quad_tree(x_start, y_start, n//2)  # 왼쪽 위
        quad_tree(x_start, y_start + n//2, n//2)  # 오른쪽 위
        quad_tree(x_start + n//2, y_start, n//2)  # 왼쪽 아래
        quad_tree(x_start + n//2, y_start + n//2, n//2)  # 오른쪽 아래

        answer += ")" # 호출이 모두 끝날 경우 괄호 닫음

n = int(input()) # 영상의 크기
grid = []
for _ in range(n):
    li = input().strip()
    li = list(map(int, li)) # 문자열을 int형으로 변환하여 리스트로 저장
    grid.append(li)

answer = ""
quad_tree(0, 0, n)

print(answer)

💡TIL

배운 점이 있다면 입력해주세요

  • 문제를 쪼개보자!!
  • 어렵게 생긴 문제라도 규칙을 찾아보기 위해 문제를 쪼개는 습관을 들여보자

@zaqquum zaqquum requested review from zaqquum and removed request for zaqquum August 5, 2024 12:00
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

깔끔한 풀이 잘 봤습니다!!

@zaqquum zaqquum merged commit 0e73631 into main Aug 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants