문제 설명
점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.
전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.
제한사항
- 전체 학생의 수는 2명 이상 30명 이하입니다.
- 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
- 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
- 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
- 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.
이 조건을 제대로 해석을 안 해서 처음 풀었을 때 100점이 나오지 않았다. lost에 들어있는 원소가 reserve에도 들어있을 수 있다는 조건.
입출력 예
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명이 체육수업을 들을 수 있습니다.
풀이 과정
- 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.
처음에 이 조건을 제대로 읽지 않아서 틀린 케이스들이 나왔다.
->
여벌 체육복이 있는 학생만 빌려줄 수 있으므로, 분실한 학생이 여벌 체육복을 들고 있는 경우, 여벌 체육복 있는 학생 리스트에서 먼저 제외하여 따로 처리하면 문제가 해결됩니다.
반례: 5, [2,5,6], [2, 4, 6] 정답 5
3,[1,2],[2,3] 답)2
이터레이터를 사용해 reserve벡터의 erase함수로 lost와 동일한 원소들을 반복문 안에서 제거하려 했으나 아래와 같은 오류가 생겼다.
"can't increment vector iterator past end"
이 오류는 iterator의 무효화가 일어나서 생기는 오류다. 아래는 오류가 발생한 코드.
for (auto it = reserve.begin(); it != reserve.end(); ++it) {
for (int j = 0; j < lost.size(); ++j) {
if (*it == lost[j]) {
reserve.erase(it); // iterator 무효화 오류
j= lost.size();
}
}
}
->
컨테이너에 원소를 추가하거나 제거하게 되면 기존에 사용하였던 모든 iterator 들을 사용할 수 없게 되기 때문이다.
다시 말해 위 경우 vec.erase(it) 을 수행하게 되면 더이상 it 은 유효한 반복자가 아니게 된다.
또한 end iterator 역시 무효화 된다.
따라서 itr != end iterator 이 비교 불가능하게 되고, 영원히 성립하여 무한 루프에 빠지게되는 것이다.
이를 해결하는 방법은 vec.erase(it)의 리턴 값을 다시 it로 대입시킨다.
또한 마지막 원소를 지운 다음 it는 마지막 원소의 다음인 vec.end()를 가리키는데
이때 for문을 나가면서 ++이 일어나지 않도록 if 문 처리를 통해 erase가 될떄는 itr을 증가시키지 못하도록 해주게 되면 된다.
아래는 iterator 무효화가 일어나지 않게 수정한 코드이다.
for (auto it = reserve.begin(); it != reserve.end();) {
bool isRemoved = false;
for (int j = 0; j < lost.size(); ++j) {
if (*it == lost[j] && !isRemoved) {
it = reserve.erase(it); // it를 erase()의 리턴값으로 초기화
lost.erase(lost.begin() + j);
isRemoved = true;
}
}
if (!isRemoved)
++it; // erase()가 될때는 it를 증가시키지 못하도록
}
->
find_if에서 조건부의 람다 함수에 return true를 해주는 경우의 분기만 쓰고 else일 경우에 return false를 안해줬더니 제대로 벡터의 end() iterator가 반환되지 않았다.
->
반례: n = 5 lost = [4,2] reserve = [3,5] 답: 5
내가 짠 로직은 좌측부터 비교해가는 것이므로 sorting이 필요했는데 이것을 간과했다.
=>
로직은 레벨1 답게 생각하기 쉬웠지만 생각지도 못한 이터레이터 사용 관련 오류에서 시간이 많이 소요되었다. c++ 공부를 확실하게 해놔야 할 것 같다.
코드
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int solution(int n, vector<int> lost, vector<int> reserve) {
int answer = 0;
sort(lost.begin(),lost.end());
sort(reserve.begin(),reserve.end());
for (auto it = reserve.begin(); it != reserve.end();) {
bool isRemoved = false;
for (int j = 0; j < lost.size(); ++j) {
if (*it == lost[j] && !isRemoved) {
it = reserve.erase(it);
lost.erase(lost.begin() + j);
isRemoved = true;
}
}
if (!isRemoved)
++it;
}
for (int i = 0; i < lost.size(); ++i) {
auto it = find_if(reserve.begin(), reserve.end(), [lost, i](int n) {
if (n == lost[i] - 1 || n == lost[i] + 1)
return true;
else
return false;
});
if (it != reserve.end()) {
reserve.erase(it);
}
else {
--n;
}
}
answer = n;
return answer;
}
필요 지식
- STL find, find_if 함수
https://wiserloner.tistory.com/320
C++ std::find, std::find_if 함수
1. find 함수 int myints[] = {10, 20, 30, 40}; int* p; p = std::find(myints, myints + 4, 30); if (p != myints + 4) std::cout << "Element found in myints: " << *p << '\n'; else std::cout << "Element n..
wiserloner.tistory.com
- STL remove_if 함수
- erase 활용: 벡터 v의 i번째 인덱스에 있는 원소를 삭제하고 싶을 때에는 v.erase(v.begin() + i);
- 컨테이너에서 특정한 술어 구문을 만족하는 객체를 모두 없애려면? : vector, string, deque이면 erase-remove_if 합성문을 사용한다.
https://cho001.tistory.com/164
C++ 벡터 특정 원소 지우는 방법 vector.erase(),remove() 등 Tips
1. Erase를 활용하는 방법 벡터 v에서 i번째 원소를 삭제하고 싶다면 erase 함수를 사용하면 된다. erase 함수의 인자는 iterator 즉, 지우고 싶은 원소의 주소이다. http://www.cplusplus.com/reference/vector/v..
cho001.tistory.com
- 람다(lambda) 표현식
-람다 캡처: 람다 외부에 정의되어 있는 변수나 상수를 람다 내부에서 사용하기 위함.
- 캡처의 두 방식: call by value, call by reference
-> 참조할때는 &기호 붙이고, 복사 할때는 그냥 변수 이름 사용
https://blockdmask.tistory.com/491
[C++] 람다 표현식, lambda에 대해서
안녕하세요. BlockDMask입니다. 오늘은 C++11, 14에서 추가된 lambda 표현식에 대해 알아보겠습니다. <목차> 1. 람다 표현식 2. 람다 표현식 사용 방법과 구조 3. 람다의 필요성, 사용 예제 1. C++ 람다 표현
blockdmask.tistory.com
알게된 것
- iterator 무효화가 일어나지 않도록 하는법
: vector의 erase() 함수는 삭제한 원소를 가리키는 pos의 다음을 가리키는 iterator 를 return 한다.
https://blog.naver.com/wldlf94/222510941824-
vector erase 할때 iterator 조심. ( 수정. )
vector erase 할때는 항상 iterator 무효화를 조심해야한다. 먼저, vector의 erase는 두가지 형태가 있다. ...
blog.naver.com
- find_if와 같은 조건부 알고리즘 함수 쓸 때 true,false 리턴 조건을 명시해줘야 한다.
'코딩테스트 및 개인공부 > 문제풀이' 카테고리의 다른 글
[백준 - 2839번] [그리디 알고리즘] 설탕 배달 (0) | 2021.11.24 |
---|---|
[프로그래머스 - level2] [탐욕법] 조이스틱 (0) | 2021.11.19 |
[프로그래머스 - level2] [완전탐색] 카펫 (0) | 2021.11.07 |
[프로그래머스 - level1] [완전탐색] 모의고사 (0) | 2021.11.03 |
[프로그래머스 - level2] [정렬] H-Index (0) | 2021.11.03 |