문제
점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.
전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.
제한사항
- 전체 학생의 수는 2명 이상 30명 이하입니다.
- 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
- 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
- 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
- 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.
문제를 보고 든 생각
두 번이나 풀어본 문제다.
나의 아이디어
여유분을 가지고 있는 학생 중 도난 당한 학생의 배열에 번호가 동시에 있는 학생을 제외하고
남은 (여유분이 있는) 학생들로 체육복을 도난 당한 학생들에게 체육복을 나눠주는 식으로 풀면 된다고 생각했다.
이 문제를 풀어봤던 기억으로는
그리디 알고리즘을 사용하여 풀 수 있는 문제로 기억이 났다.
하지만 좀 더 구현에 가깝게 풀었던 것으로 더 기억에 남았다.
오늘 이 문제를 풀면서 가장 최악이었던 것은 2번이나 이 문제를 풀어봤으나 오늘은 이 문제를 푸는 데 실패했다는 것이다.
결과
def solution(n, lost, reserve):
for r in reserve:
if r in lost:
lost.remove(r)
reserve.remove(r)
possible = n - len(lost)
for r in reserve:
if r - 1 in lost or r + 1 in lost:
if r - 1 in lost:
lost.remove(r - 1)
else:
lost.remove(r + 1)
reserve.remove(r)
possible += 1
return possible
100점 중 25점 짜리 풀이를 첫 번째 답안으로 냈다. 두 번이나 풀어봤는데 25점이라니 말도 안 나온다.
def solution(n, lost, reserve):
extra = []
for r in reserve:
if r in lost:
lost.remove(r)
else:
extra.append(r)
possible = n - len(lost)
for ex in extra:
if ex - 1 in lost or ex + 1 in lost:
if ex - 1 in lost:
lost.remove(ex - 1)
else:
lost.remove(ex + 1)
possible += 1
return possible
5번째 시도만에 90점을 받았다.
결국 100점은 받지 못했다.
이 코드를 떠올리는 데 심지어 1시간은 넘게 걸린 것 같다.
강의에서 배운 것
이 문제를 해결하는 데에는 2가지 방법이 있을 수 있다고 한다.
간단하게 분류를 하자면, 첫 번째 방법은 조건으로 주어진 학생 수가 이 문제처럼 적을 때
두 번째 방법은, 이 문제와는 달리 조건으로 걸린 최대 학생 수가 굉장히 많을 때
강의를 보고 해결 방법을 이해하기는 했는데 더 신기했던 건 내가 2번 푸는 동안 사용했던 방법은 그 두 방법 중에 없었다.
강의를 통해 알게 된 풀이
def solution(n, lost, reserve):
arr = [1] * (n + 2)
# 여벌이 있는 학생들은
# 2벌이 있다고 표시해준다.
for i in reserve:
arr[i] += 1
# 체육복이 없는 학생들은
# 리스트의 요소에 -1을 해준다.
for i in lost:
arr[i] -= 1
for i in range(1, len(arr)):
if arr[i - 1] == 0 and arr[i] == 2:
arr[i - 1 : i + 1] = [1, 1]
elif arr[i] == 2 and arr[i + 1] == 0:
arr[i : i + 2] = [1, 1]
arr = [s for s in arr[1 : n + 1] if s > 0]
return len(arr)
첫 번째 방법이다.
1. n - 1번째 학생과 n + 1번째 학생을 탐색하기 쉽게 하기 위해서 주어진 학생 수 n보다 2명 더 많은 길이의 배열을 생성한다.
2. 여벌이 있는 학생은 1을 더해주어 여벌이 있다는 의미의 숫자 2로 만들어주고 체육복이 없는 학생들로 for문을 시행하여 -1을 해준다.
3. 위 2개의 for문의 결과로 arr 안에 요소가 0이면 체육복이 없는 학생, 1이면 한 벌 있는 학생, 2면 두 벌 있는 학생을 의미할 수 있게 된다.
4. 따라서, 문제에서 나온 그대로 구현해주면 된다. n-1 번째 학생이 체육복이 없고 n번째 학생이 여벌이 있으면 [1, 1] 즉 체육복을 나눠준다. elif도 마찬가지이다.
5. 탐색이 용이하도록 넣어준 맨 앞과 맨 뒤의 두 개의 요소를 제외하고 체육복이 있는 학생들만 arr에 다시 담는다.
6. 그 arr에 몇 명의 학생이 있는지가 바로 몇 명이 수업을 들을 수 있는지를 의미하게 된다.
# 교집합을 만들기
comb = set(lost) & set(reserve)
# 여벌을 가져온 학생이 잃어버린 것을 lost에서 뺀다
set_lost = set(lost) - comb
set_reserve = set(reserve) - comb
for s in set_reserve:
if s - 1 in set_lost:
set_lost.remove(s - 1)
elif s + 1 in set_lost:
set_lost.remove(s + 1)
print(n - len(set_lost))
두 번째 방법이다.
1. set_lost에 도난 당한 학생 중 여벌을 가져왔던 학생을 제외한다.
2. set_reserve에 여벌을 가져왔던 학생 중 도난 당한 학생을 제외한다.
3. 확실히 여벌을 가지고 있는 학생들을 가지고 for문을 시행하면서 n -1 번째 학생이 도난 당한 학생 배열에 있다면 n - 1 번째 학생을 도난 당한 학생 배열에서 제거해준다. 여벌을 나눠주어 수업을 들을 수 있게 됐다는 의미이다. elif도 마찬가지이다.
4. 그리하여 도난 당했던 학생들 중 여벌을 받은 학생들이 제외됐다. 이제 총 학생 수에서 여벌을 받지 못한 학생이 남아있는 도난 당한 학생 수를 빼주면 수업을 들을 수 있는 학생 수가 나타난다.
느낀 점
1. set을 통해 차집합을 표현할 수 있다는 걸 다시금 인지할 수 있었다.
2. set과 &을 통해 교집합을 표현할 수 있다는 걸 처음 배웠다.
3. 역시 풀어냈던 문제라고 해서 내가 완전히 그 문제를 흡수했음을 말해주는 건 아닌 것 같다. 어떡하지.
'문제 풀이' 카테고리의 다른 글
타겟 넘버 (0) | 2022.10.25 |
---|---|
조이스틱 (0) | 2022.10.25 |
4일차, 큰 수 만들기 (0) | 2022.10.23 |
3일차, 가장 큰 수 (0) | 2022.10.22 |
1일차, 완주하지 못한 선수 (0) | 2022.10.19 |