점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.
전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.
n | lost | reserve | return |
---|---|---|---|
5 | [2, 4] | [1, 3, 5] | 5 |
5 | [2, 4] | [3] | 4 |
3 | [3] | [1] | 2 |
예제 #1
1번 학생이 2번 학생에게 체육복을 빌려주고, 3번 학생이나 5번 학생이 4번 학생에게 체육복을 빌려주면 학생 5명이 체육수업을 들을 수 있습니다.
예제 #2
3번 학생이 2번 학생이나 4번 학생에게 체육복을 빌려주면 학생 4명이 체육수업을 들을 수 있습니다.
이 문제는 그리디 알고리즘 카테고리에 속해있는 문제이다. 즉, 선택의 순간에서의 최적의 상황이 전체적으로도 최적이 적용되도록 출제된 문제라고 볼 수 있다.
문제를 살펴보면 인자로는 전체 학생의 수 n, 체육복을 도난당한 학생들 lost, 여벌의 체육복을 가져온 학생들 reserve가 있다. 이 상황에서 체육수업을 들을 수 있는 학생의 최댓값을 구하고자 하는 것이다.
그리디 문제이니 reserve의 한 학생이 lost학생에게 여벌의 체육복을 나눠주는 최적의 방법이 체육 수업을 들을 수 있는 학생의 최댓값을 구하는데 적합하다는것을 보장할 수 있다.
문제의 특징 중 하나는 학생들의 번호가 있는데, 빌려주는 사람 기준 앞 번호나 뒷 번호에 학생들에게 여벌의 체육복을 빌려줄 수 있다고 한다.
또한, lost학생, reserve학생 각각 모두 중복되는 번호는 없다.
그리고, 이게 가장 중요한 정보인 것 같은데,
“여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.”
위 내용을 예시로 보자면 다음과 같다.
lost = [1, 2, 3], reserve = [3,4,5] 와 같이 나온다면 3번의 학생은 여벌 체육복을 가져왔지만 도난당해 빌려줄 수 없는 상황이 되는것이다.
그렇다면 reserve에서 3번을 제외하는 것이 맞다. 또한 lost에서도 3번을 제외해야 한다. 왜냐하면 잃어버렸지만, 3번은 여벌의 체육복을 잃어버린것이지 자기것은 가지고 있기 때문이다.
그러면 이 제한 조건을 만족시키기 위해서는 reserve, lost 리스트에서 동일한 요소를 제거해야한다.
# 마지막 제한 사항: 여벌 체육복을 가져온 학생 (reserve)이 체육복을 도난당했을 수 있다.
reserve_set = set(reserve) - set(lost)
lost_set = set(lost) - set(reserve)
그 다음, 이제 확인해야 할 방법은 reserve_set으로부터 반복문을 돌리면서 해당 요소의 앞번호, 뒷번호에 해당하는 번호가 lost_set에 있다면, lost_set에서 제거하는 방식을 구현해야한다.
여기서, 또 하나 짚어야 할 점은 여벌의 체육복을 빌려주는 학생 기준, 왼쪽이 최적이냐 오른쪽이 최적이냐라는 문제가 있다.
만약, n = 5, lost = [2, 4], reserve = [3, 5]인 경우를 살펴보자.
3번학생은 2번에게도 줄 수 있고, 4번에게도 줄 수 있다. 그리고 5번학생은 4번에게 줄 수 있다. 그러면 4번은 3번에게도, 5번에게도 체육복을 받을 수 있다. 만약 3번 학생이 4번에게 줘버리면 2번은 체육복을 받을 수 없는 상황이 되기 때문에, 체육복을 양 옆 학생에게 빌려줄 수 있다면 왼쪽 요소부터 탐색해야 함을 알 수 있다.
코드 구현은 아래와 같다.
for i in reserve_set:
if i-1 in lost_set:
lost_set.remove(i-1)
elif i+1 in lost_set:
lost_set.remove(i+1)
최종적으로, 체육수업을 들을 수 있는 학생의 최댓값은 전체 학생수 n에서 lost_set에 속해있는 학생을 제외하면 된다.
최종적인 코드는 아래와 같다.
def solution(n, lost, reserve):
# 마지막 제한 사항: 여벌 체육복을 가져온 학생 (reserve)이 체육복을 도난당했을 수 있다.
reserve_set = set(reserve) - set(lost)
lost_set = set(lost) - set(reserve)
for i in reserve_set:
if i-1 in lost_set:
lost_set.remove(i-1)
elif i+1 in lost_set:
lost_set.remove(i+1)
return n - len(lost_set)
이 문제는 그리디 알고리즘을 사용해 reserve의 요소마다 local optimum인 왼쪽 요소부터 탐색하여 전체 최적의 해를 구하는 문제였다.
개인적으로 그리디 알고리즘을 1문제 풀어봤고, 어제 TIL에서도 풀고, 오늘까지 푼거면 3문제를 푼 것인데 뭔가 그리디 알고리즘의 유형이 있는것 같다.
조만간에 그리디 알고리즘 개념을 정리하고, 여러 문제를 익혀봐야겠다.
읽어주셔서 감사합니다.