티스토리 뷰
🚀 들어가며...
- 탐욕법(Greedy)를 이용하는 문제입니다.
- 난이도는 프로그래머스 기준 레벨 1 ~ 2 사이 문제입니다.
🔗 문제
https://school.programmers.co.kr/learn/courses/30/lessons/42862
📑 내용
[ 문제 설명 ]
점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.
전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.
[ 제한 조건 ]
- 전체 학생의 수는 2명 이상 30명 이하입니다.
- 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
- 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
- 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
- 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.
[ 입출력 예 ]
n | lost | reverse | return |
5 | [2, 4] | [1, 3, 5] | 5 |
5 | [2, 4] | [3] | 4 |
3 | [3] | [1] | 2 |
🔗 풀이
알고리즘 문제에는 제한사항에 답이 있다고 할 정도로 제한사항에는 많은 힌트들이 숨어있습니다. 또한 제한사항들을 보고 예외처리를 해주어야 통과할 수 있는 문제도 있으며 주어진 조건의 return 값이 0~1000까지로 정해져있어 연산 횟수를 줄일 수 있는 조건이 되기도 합니다.
제가 이번 문제의 제한 사항에서 유심히 본 내용은 "중복이 없다" 와 "여별의 체육복이 있는 학생도 도난 당했을 수 있다" 입니다. 알고리즘에 대한 지식이나 이를 다룰 줄 아는 기술도 중요하지만 정작 문제를 제대로 이해하지 못한다면 문제의 대한 접근조차 엉뚱한 곳으로 할 것입니다. 이런 면에서 저 말을 어떻게 잘 이해하느냐에 따라 이 문제를 풀 수 있냐 없냐가 갈렸던 것 같습니다.
"중복이 없다" 라는 것은 Lost 와 Reserve 내의 원소값이 unique하다는 것입니다. 즉, lost =[1,1,2] reserve=[3,3,5,4,2] 가 될 수 없습니다.
"여별의 체육복이 있는 학생도 도난 당했을 수 있다" 라는 것은 lost값에 reserve값이 공통적으로 존재할 수 있다라는 것을 의미합니다. 예를 들면, lost=[1,2,3] , reserve[3,4,5] 처럼 말이죠. 이때 여벌의 체육복은 1개밖에 없다고 가정하므로 reserve에 있는 3은 체육복을 빌려줄 수 없습니다. 따라서 lost와 reserve에 같은 값이 있다면 그 값은 reserve에서 제외시켜주어야 합니다.
여기까지 이해되셨다면 문제는 모두 푼 것이나 마찬가지입니다. 저는 먼저 "여벌의 체육복이 있는 학생도 도난 당했을 수 있다" 라는 전처리를 해주었습니다. 반복문을 통해 전체 값을 도출해낼때 애초에 Reserve값이 잘못되어있다면 틀린 값이 나오기 때문입니다.
따라서 Reserve의 요소들 중 Lost에 동일하게 존재하는 값들은 모두 제거해주어야 합니다.
set_reserve = set(reserve) - set(lost)
set_lost = set(lost) - set(reserve)
set 자료형은 중복을 허용하지 않는 집합 자료형입니다. set의 장점이라면 객체 끼리 집합 연산을 지원한다는 것인데, 다음과 같이 '-' 기호를 이용해 각 집합별로 차집합을 수행할 수 있습니다. 참고로 list 자료형은 '-' 와 같은 차집합 연산이 지원되지 않습니다. 제한 조건 중 중복을 허용하지 않는다는 lost 와 reserve이기 때문에 set자료형을 쓴다고해서 lost와 reserve값에 어떠한 변화는 없습니다.
위와 같은 전처리를 해준다면 set_reserve와 set_lost 의 비교 준비가 끝난 것입니다.
이제 입출력 예시 1번을 예로 들어 Greedy Algorithm을 해보겠습니다.
n | lost | reserve | return |
5 | [2,4] | [1,3,5] | 5 |
5명의 학생이 있다고 했을 때 체육복을 잃어버린 2,4번 학생은 각각 1,3번 3,5번 학생으로 부터 체육복을 빌려받을 수 있습니다. 그렇다면 reserve를 기준으로 왼쪽에 있는 학생부터 빌려주는것이 맞을까요 아니면 오른쪽에 있는 학생부터 빌려주는 것이 맞을까요?
n | lost | reserve | return |
5 | [2,4] | [3,5] | 5 |
다음과 같이 예시를 변형해보겠습니다. reserve에서 1이 빠진 예시인데요. 4번 학생은 3번과 5번 학생으로부터 모두 체육복을 받을 수 있습니다. 만약에 체육복을 빌려주는 방향을 reserve 요소를 기준으로 오른쪽에 있는 값을 먼저 빌려준다면 어떻게 될까요?
reserve 3을 기준으로 4번 학생은 체육복을 빌려서 체육 시간에 참가할 수 있을 것입니다. 하지만 2번학생은 3번 학생으로 부터 체육복을 받을 수 있음에도 받지 못하며, 4번학생은 3번이 빌려주지 않아도 5번으로 받을 수가 있었습니다.
이런 예시로 볼 때 체육복을 양 옆 학생에게 빌려줄 수 있다면 왼쪽 요소부터 탐색해야함을 알 수 있습니다.
따라서, reserve의 i-1 요소 즉, reserve학생의 왼쪽 학생부터 빌려주고 만약에 없다면 오른쪽 학생을 주는 식으로 알고리즘을 작성해야합니다.
💌 소스코드
def solution(n, lost, reserve):
set_reserve = set(reserve) - set(lost)
set_lost = set(lost) - set(reserve)
for i in set_reserve:
if i - 1 in set_lost:
set_lost.remove(i - 1)
elif i + 1 in set_lost:
set_lost.remove(i + 1)
return n - len(set_lost)
🙋🏻♂️ 후기
탐욕법 알고리즘을 사용해 reserve의 요소마다 local optimum인 왼쪽 요소부터 탐색하여 전체 최적의 해를 찾는 문제였습니다.
'Algorithm' 카테고리의 다른 글
[Algorithm] 유니온 파인드(Union-Find)란?? python에서 구현하기 (3) | 2022.08.26 |
---|---|
[Algorithm] DFS와 BFS 선택의 기준 (0) | 2022.08.04 |
[python][프로그래머스] 크레인 인형뽑기 게임 (0) | 2022.07.27 |
[python][프로그래머스] 큰수만들기 (0) | 2022.07.11 |
[python][프로그래머스] 문자열압축 (0) | 2022.07.04 |
- Total
- Today
- Yesterday
- static files
- JavaScript
- db
- union-find
- MVT
- lv1
- generator expression
- data formatting
- Algorithm
- Master & Slave
- ORM
- django ORM
- container
- uSWGI
- Greedy Algorithm
- 탐욕법
- This
- programmers
- JS
- Default export
- Python
- django
- lv2
- react
- list
- Named export
- docker
- PostgreSQL
- SQL
- Linux
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |