[고득점Kit][이분탐색] 징검다리 ⭐⭐⭐⭐
카테고리: Programmers
[이분탐색] 징검다리
난이도 ⭐⭐⭐⭐
💛 문제
💛 풀이
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int solution(int distance, vector<int> rocks, int n) {
int answer = 1;
int start = 1;
int end = distance;
sort(rocks.begin(), rocks.end());
while(start <= end)
{
int mid = (start + end) / 2;
int gap = 0;
int lastRock = 0;
int removeCount = 0;
for(int i = 0; i <= rocks.size(); i++)
{
if (i == rocks.size())
gap = distance - lastRock;
else
gap = rocks[i] - lastRock;
if (gap < mid)
removeCount++;
else if (i != rocks.size())
lastRock = rocks[i];
}
if (removeCount > n)
end = mid - 1;
else
{
start = mid + 1;
answer = mid;
}
}
return answer;
}
이분 탐색
- 고정 되어 정해져 있는 것은 무엇인지 (비교할 대상이 된다.)
n
제거해야 하는 바위의 수
- 무엇을 이분 탐색으로 찾을 것인가 (
mid
로 업뎃 해 나갈 것)- ‘각 지점 사이의 최소 간격’ 즉 바위간의 최소 간격.
- 의 최대값
- 찾으려고 하는 것이 속한 범위가 정렬이 되어 있는가
- ‘각 지점 사이의 최소 간격’의 범위의 최소값은 대충
1
로 상정하고 최대값은 대충 바위가 하나도 없어서 출발지점 ~ 도착지점 간의 거리인distance
로 정해놓는다. - 1 ~ distance 범위는 그 자체로 정렬이 되어 있다. 우리가 찾고자 하는 답인 ‘각 지점 사이의 최소 간격’는 이 정렬된 범위 내에 있으므로 이분 탐색을 사용하여 답을 찾을 수 있다.
int start = 1; int end = distance;
- ‘각 지점 사이의 최소 간격’의 범위의 최소값은 대충
- 찾고자 하는 것은
각 지점 사이의 최소 간격
- 분할 기준이 되는 비교는
제거해야할 바위 수
와 현재의mid
인각 지점 사이의 간격
을 기준으로 했을 때 제거 한 바위의 수를 비교.
모든 경우의 수를 검사하지 않는다. 우리가 찾고자 하는 답인 ‘각 지점 사이의 최소 간격’을 업다운 형식으로 범위를 좁혀 나가 찾을 뿐이다. 업 다운의 기준은 정해져 있는 현재의 거리가
n
개 이하로 바위를 제거하는 것이 가능한 거리인지를 따지는 것이 된다.n
개 이하로 최대한 바위를 적게 제거해야 하는 이유는 거리의 최소값들 중에 가장 최대값이 되는 제거 케이스를 찾아야 하기 때문에 최대한 바위를 적게 제거 해야 한다.
- 문제에서 명확히 정해져 있는 것은
n
제거해야 하는 바위의 수.- ⭐현재 정해놓은 '각 지점 사이의 최소 간격'인
mid
를 기준으로 했을 때의 바위 제거 가능한 수를n
과 비교하여 범위를 업데이트 및 좁혀 나간다. 👉 현재 정해둔 `mid`인 '각 지점 사이의 최소 간격' 보다 작은 간격의 바위는 제거하고 이 제거 횟수를 세자! 그리고 다 세고 난 후에n
과 비교하자!for(int i = 0; i <= rocks.size(); i++) { if (i == rocks.size()) gap = distance - lastRock; else gap = rocks[i] - lastRock; if (gap < mid) removeCount++; else if (i != rocks.size()) lastRock = rocks[i]; }
- 먼저
rocks
를 정렬하여 바위의 위치들을 순서대로 정렬한다.sort(rocks.begin(), rocks.end());
rocks
를 통해 위치별로 순서대로 바위 사이의 간격을 구한다. (바위1위치 - 출발지), (바위2위치 ~ 바위1위치), … , (마지막바위위치 - 마지막에서두번재바위위치), (도착지 - 마지막바위위치) 이렇게 총rocks.size() + 1
개의 간격을 구해야 한다. for 문은rocks.size() + 1
번 돌아야 함if (i == rocks.size())
마지막 간격은 (도착지 - 마지막바위위치) 값인distance - lastRock
.else
그 외의 간격은 모두rocks[i] - lastRock
- 바위를 제거해나갈 것이기 때문에 간격을 구할 때 제거 된 바위의 그 앞 바위를 위치를 뺄셈 해주어야 한다. 따라서 이전 바위 위치를
lastRock
변수에 저장한다. - 바위 제거하는 기준 👉
if (gap < mid) removeCount++; else if (i != rocks.size()) lastRock = rocks[i];
- 위에서 구한 간격
gap
이mid
보다 작다면- 간격을 넓혀야 하므로 바위를 제거한다.
removeCount
증가. for문이 끝나면 현재mid
에서의 바위 제거 수가removeCount
에 최종적으로 담기게 되고 후에 이걸n
과 비교하여 이분 탐색 범위를 좁혀나갈 것이다. lastRock
을 업데이트 하지 않는다. 현재 바위는 제거 되었으므로lastRock
은 그대로 이전 바위다. 현재 바위가 3바위라면 이전 바위는 2바위이고 다음 바위는 4바위 가 될텐데 4바위는 2바위와의 간격을 구하도록lastRock
을 업데이트 하지 않음.
- 간격을 넓혀야 하므로 바위를 제거한다.
- 위에서 구한 간격
gap
이mid
보다 크거나 작다면- 바위를 제거하지 않는다. 따라서
removeCount
을 업데이트 하지 않는다. lastRock
을 업데이트 한다. 현재 바위는 제거 되지 않았으므로 다음 바위에서 간격을 잴 이전 바위를 현재 바위로 업데이트 한다.- 단
lastRock
을 현재 바위로 업데이트 하는 과정은 현재 순회 위치가 도착지가 아닐 때만 이루어져야 한다. 도착지일 땐i
가rocks.size()
값이니까 이를 고려하지 않으면last = rocks[i]
에서 런타임 에러 남 !
- 단
- 바위를 제거하지 않는다. 따라서
- 위에서 구한 간격
- 먼저
- for문을 돌면서 구한 현재 정해놓은 ‘각 지점 사이의 최소 간격’인
mid
를 기준으로 구한 바위 제거 수인removeCount
를n
과 비교하여 이분 탐색 범위를 업데이트 한다.removeCount > n
: 현재 정해놓은mid
기준에서n
제거해야 하는 바위 보다 더 많이 제거 됐다면 현재 정해놓은 바위 간격인mid
가 너무 크다는 것이다. 더 작은 범위로 좁혀야 한다.if (removeCount > n) end = mid - 1;
removeCount <= n
: 현재 정해놓은mid
기준에서n
제거해야 하는 바위 보다 같거나 더 적게 제거했다면n
더 적게 제거 했다면 더 큰mid
로 간격을 더 크게 해서 더 많이 제거할 수 있도록 해야 겠지만,n
과 같다면 현재의mid
가 충분하다는 것이므로 정답 후보가 될 수 있다. 더 큰 최대값이 있을 수 있으므로, 즉 더 큰mid
에서도 정답이 있을 수 있으므로 더 큰 범위로 좁힌다.answer
를 업데이트 한다. 현재의mid
가 답이 될 수도 있으므로.. 이게 최대값이 될 수 있다.- 예를 들어 현재 간격을 5로 해두었을 때 제거한 바위의 수가
n
과 같았다면 5 가 정답이 될 수 있다. 그러나 5보다 더 큰 수에서도 제거한 바위의 수가n
과 같을 수 있기 때문에 일단answer
에 5를 저장 후 더 큰 값의 간격에서도 바위를 제거해 봐야 한다!else { start = mid + 1; answer = mid; }
- 예를 들어 현재 간격을 5로 해두었을 때 제거한 바위의 수가
- ⭐현재 정해놓은 '각 지점 사이의 최소 간격'인
answer
가 while문에서 한번도 업뎃되지 않을 경우를 대비하여 최소값인1
를 초기값으로 잡는다.- 한번도 업뎃되지 않았다는건 1 ~ 59 범위의 모든 시간이
n
명을 심사하기엔 전부 부족했다는 것이다.long long answer = end;
- 한번도 업뎃되지 않았다는건 1 ~ 59 범위의 모든 시간이
- 이 문제는 이분 탐색의 대표 코드에서
mid == answer
가 되면return
되어 빠져나오는 것과 다르게, 정확하게 딱 떨어지는 값을 찾는 것이 아닌n
개를 제거 할 수 있는 최소 간격 중에서도 최대 간격을 찾는 것이기 때문에answer
를 미리 업데이트 해둘 뿐, while을 탈출하는건start > end
가 될 때 뿐이다.mid == answer
가 되었다고해서 바로 while문을 빠져나와answer
를 리턴하면 안된다.
- 간격의 ‘최소값’을
mid
로서 정해두는 것이고 그mid
를 기준으로 구한 바위 제거 수가n
과 일치하는 것들 중 최대값이 되는mid
를 구하는 것이 목표다.- 이 문제에서 최소는 정해 두는 것이고, 최대값은 구해야 하는 것
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기