Skip to content

Minjeong / 5월 3주차 / 3문제#29

Merged
learntosurf merged 3 commits intomainfrom
minjeong
May 22, 2024
Merged

Minjeong / 5월 3주차 / 3문제#29
learntosurf merged 3 commits intomainfrom
minjeong

Conversation

@Mingguriguri
Copy link
Collaborator

@Mingguriguri Mingguriguri commented May 22, 2024

목표 문제 수: 3개

문제 유형: 그리디

푼 문제

아래는 제가 정리한 내용 중에서, 핵심이 되는 플로우와 TIL 위주를 가져왔습니다.

PGS 체육복: 그리디 / Level1

정리한 노션 링크: 노션

🔍Intuition

테스트케이스를 통해서 문제에서 접근하는 방법을 정리해보자

  1. lose리스트와 reserve 리스트에 공통으로 들어간 번호는 여벌 체육복을 가져왔는데 도난을 당한 경우이다. 즉, 도난은 당했지만 남은 체육복이 1개이기 때문에 수업에는 참여할 수 있으나 체육복은 빌려줄 수 없다. 따라서 lostlist 에서 모두 지워줘야 한다.
  2. n번의 학생이 체육복을 안 가져왔다면, n-1이나 n+1번 학생한테만 빌릴 수 있다.
  3. 위 과정을 거쳤다면 lostreserveindex순서대로 하나씩 비교하게 된다.

💡 당면한 문제점

for문 안에서 remove를 하니까 remove를 한 시점에서 반복이 종료된다.

def solution(n, lost, reserve):
    answer = 0
    
    # 중복되는 값 제거
    for l in lost:
        for r in reserve:
            if l == r:
                lost.remove(l)
                reserve.remove(r)
    print("여과된 lost:", lost)
    print("여과된 reserve:", reserve)
    # 빌려줄 수 있는지 판단
    for l in lost:
        for r in reserve:
            if l-1 == r or l+1 == r:
                lost.remove(l)
                reserve.remove(r)
                
                print("l", l, "r", r)
                print("lost", lost, "reserve", reserve)

    answer = n - len(lost)
    return answer

이렇게 for 문이 비효율적으로 반복되는 것 같아서 한 반복문 안에서 실행시켜도 결과는 똑같다. 뒤에 빌려줄 수 있는 학생들이 있어도 빌려주지 못하는 상황이 여전히 발생했다.

그래서 접근한 방식: 중복을 제거하기 위해 집합 이용

💡 집합: set() 사용

🚩플로우

  1. 집합을 이용해 중복값을 제거한다. lost_setlost 에서 reserve를 빼고, reserve_setreserve에서 lost를 뺀다.

    lose리스트와 reserve 리스트에 공통으로 들어간 번호는 여벌 체육복을 가져왔는데 도난을 당한 경우이다. 즉, 도난은 당했지만 남은 체육복이 1개이기 때문에 수업에는 참여할 수 있으나 체육복은 빌려줄 수 없다. 따라서 lostlist 에서 모두 지워줘야 한다.

  2. reserve_set 을 기준으로 reserve_set의 요소에 -1을 한 값이나 +1을 한 값이 lost_set에도 있는지 확인한다. 있다면, loset_set에서 제거한다.

    => n번의 학생이 체육복을 안 가져왔다면, n-1이나 n+1번 학생한테만 빌릴 수 있다.

  3. 전체 학생 수(n)에서 lost_set에 남아있는 학생 수를 뺀 값이 수업에 참석할 수 있는 학생 수, 즉 정답값이 된다.

🚩My submission

def solution(n, lost, reserve):
    answer = 0
    # 집합을 이용해서 중복값 제거
    lost_set = set(lost) - set(reserve)
    reserve_set = set(reserve) - set(lost)

    # 빌려줄 수 있는지 판단
    for can in list(reserve_set):
        if can-1 in lost_set:
            lost_set.remove(can-1)
        elif can+1 in lost_set:
            lost_set.remove(can+1)

    # 정답은 전체 학생 수에서 잃어버린 학생 수를 빼는 것
    answer = n - len(list(lost_set))
    return answer

💡TIL

  • 힌트로서 ‘집합’을 사용한다는 것만 보게 되었는데 명쾌하게 풀렸다. 설마 집합으로 해도 되나 가물가물했는데 과감히 접근해야겠다.

  • 공통요소를 제거할 때는 둘 중 하나만 제거하면 된다. 이때 제거할 때에는 반환하거나 연산에 사용되는 것을 제거하면 된다.

  • 집합을 사용하면

    1. 중복 요소를 제거하는 것이 편리하다
    2. 집합에서는 요소를 확인하는 작업이 상대적으로 빠르다. 보통 리스트에서 요소를 찾는 작업은 O(n)이 걸린다. 반면 집합에서 요소를 확인하는 작업은 평균적으로 O(1)이 걸린다.
    3. 또한 요소를 안정적으로 제거할 수 있다. 위에서 내가 한 시행착오처럼 리스트에서 요소를 제거할 때 리스트는 인덱스 문제로 일부 요소가 건너뛰어져서 처리가 되지 않는 경우가 발생할 수 있다. 하지만 집합을 사용하면 이런 문제를 피할 수 있다.

    출처 : [https://velog.io/@ready2start/Python-세트set의-시간-복잡도](https://velog.io/@ready2start/Python-%EC%84%B8%ED%8A%B8set%EC%9D%98-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84)


PGs 구명보트: 그리디 / Level2정리한 노션 링크: 노션

🔍Intuition

  • 한번에 1명 또는 2명만 탑승할 수 있다
  • 구명보트의 무게 제한(limit)은 항상 people 리스트의 최댓값보다 크다.

🚩플로우

  1. people리스트를 오름차순으로 정렬한다. left로 갈수록 작은 수, right로 갈수록 큰 수가 나오도록 하기 위함이다.
  2. left는 가장 왼쪽의 인덱스값인 0, right는 가장 오른쪽 인덱스값인 len(people)-1으로 초기화한다.
  3. 만약 people[left] + people[right] <= limit 이라면, answer += 1, left += 1, right -=1
  4. 그게 아니라면, people[right]가 더 크기 때문에 right-=1, answer += 1
  5. 이 과정을 rightleft보다 크거나 같을 때까지만 반복한다. 즉 rightleft의 값보다 작아지면 다 탐색한 것이므로 종료한다.
  6. answerreturn한다.

🚩My submission

def solution(people, limit):
    answer = 0
    
    people.sort()
    
    left = 0
    right = len(people)-1

    while left < right:
        if people[left] + people[right] <= limit:
            left += 1
            right -= 1
        else:
            right -= 1
            
        answer += 1
        
    if left == right:
        answer += 1
        
    return answer

💡TIL

x


백준 #1931. 회의실 배정: 그리디 / 실버1

정리한 노션 링크: 노션

🚩플로우

  1. 문제에 필요한 회의의 수(n)와 회의의 시작과 끝의 정보가 주어진 리스트(meetings)를 입력받는다.
  2. meetings리스트에 있는 모든 회의를 끝나는 시간 기준으로 정렬한다. 만약 끝나는 시간이 같다면, 시작 시간 기준으로 정렬한다.(lambda 이용)
  3. meetings리스트의 시작값(start)과 끝값(end)을 반복하여 확인한다.
    1. 이때 시작하는 값이 마지막에 끝나는 시간(last_end_time)보다 같거나 크다면, 회의를 할 수 있기 때문에 개수를 1개 늘려준다(cnt += 1) 그 후, 회의가 마지막에 끝나는 시간을 현재 값의 끝나는 시간으로 업데이트해준다.
  4. 회의의 개수(cnt)를 출력한다.

🚩My submission

import sys
input = sys.stdin.readline

n = int(input())
meetings = [[0,0] for _ in range(n)]

for i in range(n):
    start, end = map(int, input().strip().split())
    meetings[i][0] = start
    meetings[i][1] = end

# 끝나는 시간을 기준으로 정렬, 끝나는 시간이 같으면 시작 시간을 기준으로 정렬
meetings.sort(key=lambda x: (x[1], x[0]))

cnt = 0
last_end_time = 0

for start, end in meetings:
    if start >= last_end_time:
        cnt += 1
        last_end_time = end
    
print(cnt)

💡TIL

  • 그리디 알고리즘은 현재 상황에서 가장 좋은 선택을 하는 방식으로 최적의 해답을 찾는 방법이다. 따라서 시간을 기준으로 정렬해야 한다.

    • 최소한의 시간으로 최대한 많은 배정을 하기 위해서!
  • 자꾸 lambda로 정렬하는 방법을 까먹는다. 꼭꼭 기억하자ㅜㅜ

    arr = ['abc', 'bac', 'bca']
    sorted(arr, key=lambda x : x[0])
    
    arr = ['abb', 'acc', 'bcd']
    sorted(arr, key=lambda x : (x[0], x[1]))
    meetings.sort(key=lambda x: (x[1], x[0]))

@Mingguriguri Mingguriguri changed the title Minjeong / # Minjeong / 5월 3주차 / 3문제 May 22, 2024
Copy link
Collaborator

@learntosurf learntosurf left a comment

Choose a reason for hiding this comment

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

문제를 보고 어떻게 접근할지 생각하는것과 접근하고자 하는 방식대로 구현하는 것 두가지가 다 잘되어야 알고리즘 문제를 풀 수 있는 것 같아요. 그런 의미에서 바로바로 잘 구현하시는 것 같아서 대단하십니다..👍 알고리즘 문제 푼 이후에 TIL 적어놓는 것 리뷰하기에 좋은 방법인것같아요! 이번주에도 수고하셨습니다 🔥

@learntosurf learntosurf merged commit 0fa3576 into main May 22, 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