Skip to content

Minjeong / 11월 3주차 / 5문제#90

Merged
zaqquum merged 5 commits intomainfrom
minjeong
Nov 18, 2024
Merged

Minjeong / 11월 3주차 / 5문제#90
zaqquum merged 5 commits intomainfrom
minjeong

Conversation

@Mingguriguri
Copy link
Collaborator

@Mingguriguri Mingguriguri commented Nov 17, 2024

주 목표 문제 수: 5개

푼 문제


백준 #13417. 카드 문자열: 그리디, 문자열, 덱 / 실버3

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

🚩플로우 (선택)

해결전략

  • 특정 한 카드를 기준으로 하지 않고, 매번 새로운 카드를 가져올 때마다 현재 문자열의 가장 앞 글자와 비교하여 알파벳을 맨 왼쪽이나 오른쪽에 배치해야 한다.

    (난 비록, 가장 첫 카드가 기준이 되는 건 줄 알고, 틀렸다만..)

  • 가져온 카드가 현재 문자열의 가장 앞 글자보다 작거나 같으면 왼쪽에, 그렇지 않으면 오른쪽에 배치한다.

  • 리스트에서 맨 앞에 있는 값을 가져와야 하므로 deque 자료형과 popleft 함수를 활용한다.

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

  1. 테스트케이스 수 t만큼 아래 과정을 반복한다.
  2. 카드 개수 n을 입력받고, 카드를 alphabet 리스트에 저장한 뒤 deque 자료형으로 변환한다.
  3. 첫 번째 카드를 꺼내어 answer에 저장한다.
  4. alphabet 리스트의 요소가 모두 없어질 때까지 아래 과정을 반복한다.
    1. alphabet에서 가장 앞 카드(current)를 꺼낸다.
    2. answer의 가장 앞 문자와 current를 비교하여 current가 더 작거나 같으면 맨 앞에, 그렇지 않으면 맨 뒤에 추가한다.
  5. 반복이 끝나면 사전 순으로 가장 빠른 문자열 answer를 출력한다.

🚩제출한 코드

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

t = int(input())
for _ in range(t):
    n = int(input())  # 거스름돈 액수
    alphabet = list(map(str, input().split()))
    alphabet = deque(alphabet)
    answer = alphabet.popleft() # 정답 문자열. 첫 번째 카드로 시작

    while alphabet:
        current = alphabet.popleft()
        if current <= answer[0]:  # 가장 앞의 문자와 비교. 더 앞에 있다면 가장 왼쪽에 붙이기
            answer = current + answer
        else:
            # 뒤에 있다면 가장 오른쪽에 붙이기
            answer += current

    print(answer)

💡TIL

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

  • 처음에 문제에서의 예시가 MKU로 짧았고, 다른 예시들을 보았을 때에도 맨 처음에 있던 카드가 고정되어 있다고 판단했다. 이를 기준으로 코드를 작성했더니 틀렸다는 결과가 나왔다. 알고보니 가장 첫 카드를 기준으로 하는 것이 아니라 매번 가장 왼쪽에 있는 카드를 기준으로 두어야 하는 것이었다.
  • 그리디 유형에서는 자꾸 문제에서 요구하는 것을 제대로 파악하지 못하는 것 같다. 문제를 읽어도 이 문제에서 요구하는 것을 제대로 파악을 못한다. 요구사항을 정확히 파악하는 것이 중요하다는 것을 다시 느꼈다. 앞으로 문제를 풀 때는 문제를 정확히 요약하고, 요구사항을 정리한 후 코드 작성에 들어가야겠다.
  • 다른 분의 풀이를 보면서 deque 자료형에 appendleft() 함수도 있다는 것을 알게 되었다. 다음에 나도 적절한 때에 활용해봐야겠다.

백준 #2847. 게임을 만든 동준이: 그리디 / 실버4

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

🚩플로우 (선택)

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

해결 아이디어

  1. 레벨을 뒤에서부터 순차적으로 접근하며 각 레벨의 점수를 조정한다.
    • 앞에서부터 낮추게 되면 다시 앞의 난이도가 뒤의 난이도보다 높아지는 경우가 발생하게 된다. 이렇게 되면 난이도를 줄여음에도 난이도가 더 작아지게 된다.
    • 이를 해결하려면 여러 번 반복을 수행해야 하며 기하 급수적으로 시간 복잡도가 증가하게 될 수도 있다.
  2. 앞 레벨의 점수가 뒤 레벨보다 크거나 같으면, 뒤 레벨의 점수보다 1 작아지도록 점수를 감소시킨다.
  3. 감소 횟수는 최소화해야 하므로, 매 단계에서 필요한 만큼만 감소시킨다.

코드 플로우

  1. N과 각 레벨의 점수 리스트 scores를 입력받는다.
  2. 감소 횟수를 저장할 변수 answer를 초기화한다.
  3. 가장 높은 레벨부터 한 단계씩 낮춰가며 조건을 확인한다.
  4. 가장 높은 레벨부터 시작하여 다음 레벨과 점수를 비교하면서, 필요한 경우 점수를 조정하고 answer에 감소 횟수를 누적한다.
  5. 최종적으로 감소 횟수 answer를 출력한다.
  6. 모든 레벨을 확인한 뒤, 최종적으로 answer를 출력한다.

🚩제출한 코드

N = int(input())
scores = [int(input()) for _ in range(N)]

# 감소 횟수 변수
answer = 0

# 가장 높은 레벨부터 시작해서 점수를 조정
for i in range(N - 2, -1, -1):  # 뒤에서 두 번째 레벨부터 첫 번째 레벨까지
    if scores[i] >= scores[i + 1]:  # 다음 레벨 점수보다 크거나 같다면
        answer += scores[i] - (scores[i + 1] - 1)  # 감소해야 하는 횟수 증가
        scores[i] = scores[i + 1] - 1  # 다음 레벨보다 1 작게 설정

# 결과 출력
print(answer)

💡TIL

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

  • "특정 레벨의 점수를 감소시켜서, 각 레벨을 클리어할 때 주는 점수가 다음 레벨의 점수보다 커지도록 만들어야 한다."라고 나와있는데 이를 나는 “오름차순”으로 이해했다.. 근데 다시 살펴보면 레벨이 진행될수록 점수가 작아져야 함을 의미하기 때문에 내림차순으로 정렬되도록 만들어야 한다.
  • 규칙 찾아낸 것을 코드로 옮기는 것이 아직 어렵다..

백준 #31926. 밤양갱: 그리디 / 실버1

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

🚩플로우 (선택)

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

해결 아이디어

  1. n이 1일 때는 기본적으로 8초가 필요하고, 마지막에 daldida(n)을 추가하는 데 2초가 필요하므로 기본적으로 10초가 필요하다.
  2. 이후 n이 2, 4, 8, ... 등 2의 거듭제곱에 해당할 때마다 시간이 1초씩 추가된다.

즉, 필요한 총 시간은 기본 시간 10초n의 거듭제곱 레벨에 해당하는 추가 시간을 더한 값이 된다.

플로우

  1. n을 입력받는다.

  2. 구해야 하는 최소 시간은 answer 변수로 설정하고, 기본 시간은 10초로 설정한다.

    ( (d)(a)(l)(d)(i)(dal)(g)(o) 8초와 (daldida)(n) 2초로 총 10)

  3. n이 2의 거듭제곱이 될 때마다 시간을 1 증가시키기 위해서 n을 계속 절반씩 나누어가면서 1이 될 때까지 반복한다.

  4. 반복 횟수에 따라 answer를 누적하여 증가시킨다.

  5. 구한 answer 값을 출력한다.

🚩제출한 코드

import sys
input = sys.stdin.readline

n = int(input())

# 기본 시간은 10초
answer = 10

# n이 2의 거듭제곱이 될 때마다 시간을 1씩 증가시킴
while n > 1:
    n //= 2
    answer += 1
print(answer)

💡TIL

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

  • 최소 시간을 구해야 했지만 처음에는 단순히 8 + (n-1) + 2로 계산했다. 계산은 되었지만 최소 시간이 아니었다는 점을 나중에 깨달았다. 이 점을 문제 처음 풀 때 알았어야 했는데 그걸 몰라서 좀 헛걸음했던 것 같다.
  • 예제에서 주어진 예시로만 규칙을 찾으려 했지만, n = 1부터 나열해 보면서 규칙을 찾아야 한다는 교훈을 얻었다. 나열해 본 결과 규칙을 찾기 쉬워졌고, 반례를 빠르게 찾아낼 수 있었다.
  • 규칙을 발견했어도 이를 수식으로 표현하기는 어려웠다. 2의 거듭제곱이 될 때마다 시간이 증가해야 했는데, 이를 파악하기가 쉽지 않아 결국 힌트를 참고하여 해결했다. 심지어 생각보다 간단했다. 해당 숫자를 2로 계속 나누면 되었다.. 다음에는 힌트 없이도 그리디 문제를 해결하는 것이 목표다.
  • 이 문제를 통해 애드 훅 알고리즘(Ad hoc algorithm)에 대해 알게 되었다.
    • 애드 훅 알고리즘은 특정 문제를 해결하기 위해 즉석에서 고안된 알고리즘이다. '애드 훅'은 라틴어로 "이 목적을 위해"라는 뜻으로, 다른 문제에는 잘 적용되지 않는 특수한 해결 방법이다.
    • 애드 훅 알고리즘의 주요 특징은 1) 일회성, 2) 단순성, 3) 문제 특화, 4) 일반성 부족이다.
    • 주로 다음과 같은 경우에 사용된다:
      • 문제의 제약 조건이 명확하고 제한적일 때
      • 문제의 성격이 복잡해 일반적인 알고리즘을 적용하기 어려울 때
      • 시간이나 공간 복잡도를 크게 개선할 필요가 없을 때

백준 #2212. 센서: 그리디 / 골드5

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

🚩플로우 (선택)

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

풀이 아이디어

  1. 센서 위치 정렬: 센서 위치를 오름차순으로 정렬하여 인접한 거리 차이를 쉽게 계산한다.
  2. 거리 차이 계산: 정렬된 위치에서 인접한 센서들 간의 거리를 계산해 distances 리스트에 저장한다.
  3. 가장 큰 거리 차이 기준으로 구간 나누기: 센서를 k개의 구간으로 나누기 위해, 거리 차이를 큰 순서로 정렬한 후, 가장 큰 거리 차이부터 순차적으로 제거해 k개의 구간을 만든다.
  4. 최소 거리 합 계산: 나눠진 구간들의 거리 합을 구해 최소화된 수신 가능 영역의 거리 합을 구한다.

나는 여기서 구간을 나누는 것을 슬라이싱으로 접근했다.

플로우

  1. 센서의 개수 n, 집중국의 개수 k, 센서의 좌표 리스트 pos를 입력받는다.
  2. pos 리스트를 중복 정렬을 제거하고 정렬한다.
  3. 인접한 센서들 간의 거리 차이를 계산하여 distances 리스트에 저장한다.
  4. 거리 차이를 내림차순으로 정렬한다.
  5. k-1부터 마지막 요소 부분 합을 구하여 k개의 집중국을 세울 때 최소화되는 수신 가능 영역의 거리 합을 계산하여 min_dist_sum에 저장한다.
  6. min_dist_sum을 출력한다.

🚩제출한 코드

import sys
input = sys.stdin.readline

n = int(input()) # 센서의 개수
k = int(input()) # 집중국의 개수
pos = list(map(int, input().split())) # 센서의 좌표

pos = sorted(set(pos)) # 센서 위치 정렬 및 중복 제거
distances = [] # 센서 간 거리 차이

# 센서 간 거리 차이 계산
for i in range(1, len(pos)):
    distances.append(pos[i] - pos[i-1])

# 거리 차이 큰 순서로 정렬
distances.sort(reverse=True)

# 가장 큰 (k-1)개의 거리 차이를 빼고 남은 거리들의 합이 최소가 되는 거리 합
min_dist_sum = sum(distances[k-1:])

print(min_dist_sum)

💡TIL

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

  • 그리디로 푸는 방식은 정말 일반적인 방법이 아니라 아이디어와 패턴을 찾는 싸움인 것 같다. 이 문제는 아이디어가 잘 안 떠올랐다. 초기 접근 방식이 쉽지 않은 것 같다..
  • 문제를 풀 때 예외처리가 할 부분이 있는지 봐야겠다.
  • 그래도 저번에 회고 작성했던대로 문제를 읽고 바로 풀지 않고, 문제 내용을 정리한 후 접근하니 문제를 다르게 해석하고 잘못 이해하는 것은 없었다. 앞으로도 이렇게 해야겠다.

백준 #1374. 강의실: 그리디, 힙 / 골드5

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

🚩플로우 (선택)

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

풀이 아이디어

  • 강의 시작 시간을 기준으로 정렬한 후에, 앞 강의 종료 시간 중 가장 작은 시간보다 크면 카운트해야 한다. 이를 어떻게 구현해야 할 지 몰라서 결국 찾아보게 되었다. 파이썬에서는 이를 heapq를 이용하면 됐다.
  1. 정렬 및 우선순위 큐 활용
    • 강의의 시작 시간을 기준으로 정렬한다.
    • 끝나는 시간을 저장하는 최소 힙(min-heap)을 사용하여, 가장 빨리 끝나는 강의실의 정보를 관리한다.
    • 새로 시작하는 강의가 가장 빨리 끝나는 시간보다 늦게 시작하면 해당 강의실을 재사용하고, 그렇지 않으면 새로운 강의실이 필요하다.
  2. 구현 방식
    • 강의들을 시작 시간 기준으로 정렬한다.
    • 최소 힙(min_heap)에 종료 시간을 저장하며, 새로 시작하는 강의의 시작 시간을 힙의 최솟값과 비교한다.
    • 최소 힙의 크기를 유지하며, 그 크기의 최대값이 필요한 강의실의 최소 개수가 된다.

플로우

  1. 강의 개수 n과 강의 정보 rooms를 입력받아 저장한다.
  2. rooms는 강의 시작 시간을 기준으로 정렬한다.
  3. 최소힙을 저장하는 리스트 min_heap을 초기화한다. 이 힙은 강의 종료 시간을 저장한다.
  4. 최소 강의실 개수를 구할 cnt도 초기화한다.
  5. 강의 리스트(rooms)를 순회하며,
    1. 힙(min_heap)에 값이 존재하고, min_heap의 첫 값(현재 강의의 시작 시간)이 가장 빠르게 끝나는 시간보다 크거나 같으면, 해당 강의를 힙에서 pop한다. (해당 강의는 기존 가장 일찍 끝나는 시간이 지나 이어서 강의할 수 있기 때문)
    2. 현재 가으이의 종료 시간을 힙(minheap)에 추가한다.
    3. 매번 min_heap의 길이, 즉 강의 수를 구해서 그 중에서 가장 max값을 구하여 최소 강의실 수를 구해 cnt에 값을 업데이트한다.
  6. 필요한 최소 강의실 수 cnt를 출력한다.

🚩제출한 코드

import sys
import heapq
input = sys.stdin.readline

n = int(input())
rooms = []

# 강의 정보 입력받기
for _ in range(n):
    no, start, end = map(int, input().split())
    rooms.append((start, end))

# 시작 시간 기준으로 정렬
rooms.sort()

# 최소 힙 초기화
min_heap = []

for start, end in rooms:
    # 현재 강의가 가장 빨리 끝나는 강의 이후 시작하면 해당 강의 종료
    if min_heap and min_heap[0] <= start:
        heapq.heappop(min_heap)
    heapq.heappush(min_heap, end)

# 필요한 최소 강의실 개수는 힙의 크기
print(len(min_heap))

💡TIL

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

  • 배운 점:
    • 이 문제를 통해 힙(Heap) 자료구조를 다시 복습할 수 있었다. 특히, Python의 heapq 모듈은 최소 힙 구현이 간편하다는 장점이 있다.
    • 시작 시간 기준 정렬과 종료 시간 비교의 핵심 아이디어를 확실히 이해할 수 있었다.
    • heapq 모듈을 활용하여 강의실 개수를 효율적으로 계산했다.
  • 느낀 점:
    • lambda를 이용해 정렬하는 건 아직도 가물가물하다..
    • 처음에는 문제의 요구사항을 제대로 이해하지 못했지만, 예제와 힙 자료구조를 활용하면서 직관적으로 접근할 수 있었다.
    • 강의 번호는 불필요하므로 제거해 코드가 더 간결해질 수 있다는 점을 깨달았다.
    • 정렬과 힙을 결합한 알고리즘은 다양한 스케줄링 문제에 응용 가능하다.

@Mingguriguri Mingguriguri self-assigned this Nov 17, 2024
@zaqquum zaqquum merged commit 8f2cf3f into main Nov 18, 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