Skip to content

Comments

Minjeong / 1월 2주차 / 5문제#119

Merged
Mingguriguri merged 5 commits intomainfrom
minjeong
Jan 13, 2025
Merged

Minjeong / 1월 2주차 / 5문제#119
Mingguriguri merged 5 commits intomainfrom
minjeong

Conversation

@Mingguriguri
Copy link
Collaborator

@Mingguriguri Mingguriguri commented Jan 11, 2025

🌱WIL

이번 한 주의 소감을 작성해주세요!

  • 이번 주에는 프로그래머스에서 스킬체크 Level 4 문제를 풀었다. 2문제 모두 DP 문제였다. 평소에 풀던 DP는 규칙성이 쉽게 보여서 DP 유형이 쉽다고 생각했었는데, Level 4에서의 DP는 규칙성이 보이지 않아 많이 헤맸었다. DP 문제를 풀면서 카탈란 수를 알게 되었다. 처음에는 카탈란 수가 무엇인지 어떻게 이러한 규칙이 생기게 된 것인지 이해가 가지 않았다. 이제야 다양한 DP 무제를 풀어야 하는 이유를 이해할 수 있게 되었다. DP 문제를 풀며 그 안에 주로 사용되는 수학적 공식이나 점화식을 알아두는 것이 나중에 도움이 될 것 같다.
  • 문제를 풀면서 deque 타입이나 리스트를 복사하고 싶을 때 바로 대입했었는데 그러면 새로 복사되는 것이 아니라 참조하게 되어 내가 원하는대로 코드가 동작하지 않다는 것을 알게 되었다. 특히 deque 타입을 복사하고 싶다면 queue.copy()를 사용하거나 deque(queue)로 새로운 deque를 생성해야 한다는 점을 알게 되었다.

🚀주간 목표 문제 수: 5개

푼 문제


프로그래머스 단어 퍼즐: DP / Level4

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

🚩플로우 (선택)

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

  1. 초기 설정
    • dp 배열을 1e9로 초기화하고, dp[0]은 0으로 설정한다.
    • 이는 문장이 완성되지 않았다는 것을 의미한다.
  2. 단어 조각 확인
    • strsset으로 변환하여 포함 여부를 빠르게 확인한다.
  3. DP 배열 업데이트
    • i 위치에서 최대 5글자까지의 부분 문자열을 확인한다.
    • 부분 문자열이 단어 조각에 포함되면 dp[i] 값을 갱신한다.
  4. 최종 결과 반환
    • dp[n]이 초기값 그대로라면 문장을 완성할 수 없으므로 -1을 반환한다.

🚩제출한 코드

def solution(strs, t):
    n = len(t)
    dp = [1e9] * (n + 1)  # 1e9는 불가능한 경우를 의미
    dp[0] = 0  # 초기값 설정: 시작점은 0
    
    # strs를 set으로 변환해 빠르게 포함 여부 확인
    str_set = set(strs)
    
    # DP 진행
    for i in range(1, n + 1):
        # 최대 5글자까지 확인
        for j in range(1, 6):
            if i - j >= 0 and t[i - j:i] in str_set:
                # 현재 위치를 j 길이만큼 뒤로 가서 비교
                dp[i] = min(dp[i], dp[i - j] + 1)
    
    # 결과 반환: 완성 가능하면 dp[n], 불가능하면 -1
    return dp[n] if dp[n] != 1e9 else -1

💡TIL

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

  • 최댓값을 만들어야 할 때 1e9밖에 생각이 안 났는데, sys.maxsize를 이용할 수 있다는 것을 알게 되었다.
  • DP 문제를 풀 때, 상태 정의와 초기 설정이 중요하다는 것을 다시 한 번 느꼈다.
  • 부분 문자열의 최대 길이가 5라는 점을 활용해 탐색 범위를 줄이는 것이 핵심 포인트였다.
  • 동적 프로그래밍의 기본 원리인 "부분 문제의 해를 저장하고 재활용"하는 접근법이 익숙해졌다.

프로그래머스 올바른 괄호의 갯수: DP / Level4

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

🚩플로우 (선택)

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

  1. DP 초기화
    • DP 테이블 dp를 생성하고 초기값 dp[0] = 1을 설정한다.
    • dp[0]은 괄호가 없는 상태로, 유일하게 올바른 괄호가 되는 경우이다.
  2. 점화식 적용
    • dp[1]부터 dp[n]까지 점화식을 적용하여 값을 채운다.
    • 각 단계에서 점화식(dp[i] += dp[j] * dp[i-j-1])을 수행한다.
  3. 결과 반환
    • dp[n]을 반환하여 n개의 괄호 쌍으로 만들 수 있는 올바른 괄호 문자열의 개수를 반환한다.

🚩제출한 코드

def solution(n):
    # DP 테이블 초기화
    dp = [0] * (n + 1)
    dp[0] = 1  # 초기값 설정 (괄호가 없는 경우)
    
    # 카탈란 수 점화식 적용
    for i in range(1, n + 1):
        for j in range(i):
            dp[i] += dp[j] * dp[i - j - 1]
    
    return dp[n]  # n개의 괄호쌍으로 만들 수 있는 경우의 수 반환

💡TIL

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

  • 이 문제는 카탈란 수를 모르고 문제를 풀게 된다면 규칙이 보이지 않아 매우 풀기 어려운 문제이나, 카탈란 수를 알고 문제를 보게 된다면 해당 문제를 규칙을 알고 풀기 때문에 점화식을 금방 쓸 수 있는 문제이다.
  • DP 문제는 항상 규칙을 발견하기 전까지 많은 시행착오와 시간이 소모가 되는데 이렇게 스킬체크 문제로 만나니까 더 어려움을 겪었다. 문제를 마무리하고 카탈란 수를 적용하면 된다는 것을 보았지만 카탈란 수가 무엇인지 어떻게 이러한 규칙이 생기게 된 것인지 이해가 가지 않았다. 이제야 다양한 DP 무제를 풀어야 하는 이유를 이해할 수 있게 되었다. DP 문제를 풀며 그 안에 주로 사용되는 수학적 공식이나 점화식을 알아두는 것이 나중에 도움이 될 것 같다.
  • 추가로 카탈란 수를 DP를 사용하지 않고 구현할 경우 시간 복잡도는 O(2^n)이 된다. 이 문제에서는 n ≤ 14 로 풀이가 가능하지만 n이 50만을 넘어가면 연산량이 1얼을 넘어가게 된다. 그렇기 때문에 DP로 O(n^3) 으로 만들 수 있도록 최적화하는 것이 좋을 것 같다.
  • 새로운 개념을 배울 수 있는 좋은 문제였다.

🔎 배운 점

  1. 카탈란 수의 의미
    • 괄호 쌍 문제는 물론, 트리 구조, 스택 순열 등 다양한 곳에서 활용된다.
  2. DP를 통한 효율적인 해결
    • 단순히 재귀로 풀 경우 $O(2^n)$이지만, DP를 사용해 $O(n^2)$으로 해결 가능하다.
  3. 문제 접근법의 중요성
    • 문제를 단순 구현으로 접근하지 않고, 수학적 규칙을 찾아내는 과정이 필요하다.

백준 #1874. 스택 수열: 자료구조 / 실버2

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

🚩플로우 (선택)

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

  1. 초기화

    • deque를 사용해 스택을 구현한다. deque는 양쪽에서 삽입, 삭제가 가능해 pop 연산이 빠르다.
    • stack : 숫자를 임시로 담을 스택
    • answer : 연산(+, -) 기록용
    • current : 스택에 넣을 현재 숫자 (1부터 시작)
    • flag : 수열을 만들 수 있는지 판단하는 변수
  2. 반복문 진행

    • 입력받은 num에 도달할 때까지 current를 계속 push(+)한다.
    • currentnum보다 작으면 계속 push하고, 크거나 같아지면 멈춘다.
    • 이후, 최상단의 값이 num과 같으면 pop(-)하고, 다르면 NO를 출력한다.
  3. 출력

    • flagTrue인 경우 연산이 저장되어 있는 answer 리스트의 값을 차례로 출력한다.
    • False라면 NO를 출력한다.

🚩제출한 코드

import sys
from collections import deque

input = sys.stdin.readline

n = int(input())  # 수열의 길이
stack = deque([])  # 스택 초기화
answer = deque([])  # 연산 기록
flag = True  # 수열을 만들 수 있는지 확인하는 플래그
current = 1  # 현재 push할 숫자

# n번 반복하며 수열 입력
for _ in range(n):
    num = int(input())
    
    # 현재 숫자가 수열의 값에 도달할 때까지 push
    while current <= num:
        stack.append(current)
        answer.append("+")
        current += 1

    # 스택 최상단이 수열의 값과 일치하면 pop
    if stack[-1] == num:
        stack.pop()
        answer.append("-")
    else:
        # 만들 수 없는 경우
        flag = False

# 결과 출력
if flag:
    for a in answer:
        print(a)
else:
    print("NO")

💡TIL

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

  • 이 문제는 스택의 기본 개념을 활용해 수열을 만들 수 있는지 판단하는 문제이다.
  • 카탈란 수와 관련한 문제를 풀며 추천 문제로 이 문제를 풀게 되었다. 이 문제는 올바른 수열을 만들기 위해 LIFO 특성을 잘 이해해야 한다.

백준 #17413. 단어 뒤집기 2: 구현 / 실버3

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

🚩플로우 (선택)

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

  1. 입력 문자열 S를 deque로 변환하여 처리한다.
  2. 태그(< >) 여부를 tag 플래그로 관리한다.
  3. 반복문을 통해 한 문자씩 처리하며 조건에 따라 다음을 수행한다:
    • 태그 시작(<)이면 플래그를 활성화하고 태그 내용을 그대로 answer에 추가한다.
    • 태그 종료(>)이면 플래그를 비활성화하고 태그를 닫는다.
    • 공백( ``)이면 현재 단어를 뒤집어서 결과에 추가한다.
    • 태그 외의 문자는 temp에 저장한다.
  4. 반복이 끝나면 temp에 남아 있는 단어를 뒤집어서 결과에 추가한다.
  5. 결과를 문자열로 변환하여 출력한다.

🚩제출한 코드

import sys
from collections import deque

input = sys.stdin.readline

# 입력 문자열 처리
S = deque(input().strip())
temp = deque([])  # 단어를 저장하는 임시 리스트
answer = deque([])  # 결과 저장 리스트

tag = False  # 태그 상태 확인

while S:
    # 태그 시작
    if S[0] == "<":
        tag = True

    # 태그 시작 또는 공백 처리
    if S[0] == " " or S[0] == "<":
        # temp에 저장된 단어 뒤집기
        while temp:
            answer.append(temp.pop())
        # 태그 내용 추가
        if tag:
            while S[0] != ">":
                answer.append(S.popleft())
            tag = False
        # 공백 또는 닫는 태그 추가
        answer.append(S.popleft())
    else:
        # 단어를 temp에 저장
        temp.append(S.popleft())

# 남아 있는 단어 처리
while temp:
    answer.append(temp.pop())

# 결과 출력
print(''.join(answer))

💡TIL

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

  • 처음에는 단순 문자열 문제로 생각했지만, 태그와 단어 처리를 동시에 해야 해서 자료구조와 플래그를 활용한 구현이 필요했다.
  • deque를 사용하여 삽입/삭제를 효율적으로 처리했지만, 더 간결한 구현 방법도 고민해 볼 필요가 있다.

백준 #16953. A → B: 그리디 / 실버2

정리한 링크: [바로가기]

🚩플로우 (선택)

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

  1. 입력 및 초기화
    • 시작 값 A와 목표 값 B를 입력받는다.
    • queue에 시작 값 A를 넣고, 연산 횟수를 나타내는 cnt를 1로 초기화한다.
  2. BFS 탐색
    • 현재 큐에 있는 값을 모두 탐색하며 다음을 수행한다:
      1. 현재 값이 B와 같다면, cnt를 출력하고 프로그램을 종료한다.
      2. 현재 값에 대해 가능한 두 연산 결과를 계산한다:
        • 값에 2를 곱한다.
        • 값의 끝에 1을 추가한다.
      3. 연산 결과가 B를 초과하지 않으면 큐에 추가한다.
  3. 실패 조건
    • 큐가 비었음에도 B에 도달하지 못하면 -1을 출력한다.

🚩제출한 코드

import sys
from collections import deque
input = sys.stdin.readline

# 입력 받기
A, B = map(int, input().split())

# 큐 초기화
queue = deque([A])
cnt = 1 # 연산 횟수

while queue:
    # 현재 큐에서 모든 값을 탐색
    for _ in range(len(queue)):
        current = queue.popleft()

        # 현재 값이 B와 같으면 결과 출력 후 종료
        if current == B:
            print(cnt)
            exit()

        # 다음 값을 큐에 추가 (B를 초과하지 않는 경우만)
        if current * 2 <= B:
            queue.append(current * 2)
        if int(str(current)+"1") <= B:
            queue.append(int(str(current)+"1"))

    # 연산 횟수 증가
    cnt += 1

# 큐가 비었다면 -1 출력
print(-1)

💡TIL

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

  • 이 문제를 처음 어떻게 접근해야 할지 감이 오지 않아 문제 하단의 유형을 참고했다. BFS로 접근하는 방식이 가능하다는 것을 깨달았고, 이런 유형도 BFS로 풀 수 있다는 점을 배울 수 있었다.
    • 다른 분의 풀이를 통해 BFS뿐만 아니라 그리디 방식으로 규칙을 발견해 풀이할 수도 있다는 것을 알게 되었다. 특히, while-else를 활용해 특정 조건에서 자연스럽게 루프를 종료하는 방법도 배울 수 있었다.
    • 다른 분의 BFS 풀이에서는 나처럼 복잡하게 구현하지 않고, while문과 continue, else를 적절히 사용해 간결하게 문제를 해결한 점이 인상적이었다. 조건을 잘 정리하는 것이 중요하다는 점을 다시 느꼈다.
  • 처음에는 temp를 만들어 큐를 복사하려 했는데, temp = queue로 대입하면 복사가 아닌 참조만 생성된다는 점을 간과했다. 이로 인해 temp를 수정할 때 queue에도 영향을 미쳐 무한루프가 발생했다. 이를 해결하기 위해 queue.copy()를 사용하거나 deque(queue)로 새로운 deque를 생성해야 한다는 점을 알게 되었다. 이 과정은 문제 풀이와 직접 관련되지는 않지만, 데이터 구조의 동작 방식을 이해하는 데 매우 유익한 경험이었다.
  • 앞으로는 deque 타입이나 리스트를 복사할 때 참조만 생성되지 않도록 주의해야겠다.
  • 큐에서 필요한 값만 추가하는 방식으로 효율적으로 구현할 수 있었다. B를 초과하지 않는 값만 큐에 추가함으로써 최솟값을 만들 수 없을 경우를 자연스럽게 처리할 수 있었다. 큐에는 필요한 값만 넣는 것이 중요하다는 점을 다시 한번 깨달았다.

@Mingguriguri Mingguriguri merged commit b7eb73f into main Jan 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants