문제 출처
https://school.programmers.co.kr/learn/courses/30/lessons/64062
프로그래머스
SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프
programmers.co.kr
문제 설명
[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]
카카오 초등학교의 "니니즈 친구들"이 "라이언" 선생님과 함께 가을 소풍을 가는 중에 징검다리가 있는 개울을 만나서 건너편으로 건너려고 합니다. "라이언" 선생님은 "니니즈 친구들"이 무사히 징검다리를 건널 수 있도록 다음과 같이 규칙을 만들었습니다.
- 징검다리는 일렬로 놓여 있고 각 징검다리의 디딤돌에는 모두 숫자가 적혀 있으며 디딤돌의 숫자는 한 번 밟을 때마다 1씩 줄어듭니다.
- 디딤돌의 숫자가 0이 되면 더 이상 밟을 수 없으며 이때는 그다음 디딤돌로 한 번에 여러 칸을 건너뛸 수 있습니다.
- 단, 다음으로 밟을 수 있는 디딤돌이 여러 개인 경우 무조건 가장 가까운 디딤돌로만 건너뛸 수 있습니다.
"니니즈 친구들"은 개울의 왼쪽에 있으며, 개울의 오른쪽 건너편에 도착해야 징검다리를 건넌 것으로 인정합니다.
"니니즈 친구들"은 한 번에 한 명씩 징검다리를 건너야 하며, 한 친구가 징검다리를 모두 건넌 후에 그다음 친구가 건너기 시작합니다.
디딤돌에 적힌 숫자가 순서대로 담긴 배열 stones와 한 번에 건너뛸 수 있는 디딤돌의 최대 칸수 k가 매개변수로 주어질 때, 최대 몇 명까지 징검다리를 건널 수 있는지 return 하도록 solution 함수를 완성해 주세요.
[제한사항]
- 징검다리를 건너야 하는 니니즈 친구들의 수는 무제한이라고 간주합니다.
- stones 배열의 크기는 1 이상 200,000 이하입니다.
- stones 배열 각 원소들의 값은 1 이상 200,000,000 이하인 자연수입니다.
- k는 1 이상 stones의 길이 이하인 자연수입니다.
[입출력 예]

입출력 예에 대한 설명
입출력 예 #1
첫 번째 친구는 다음과 같이 징검다리를 건널 수 있습니다.

첫 번째 친구가 징검다리를 건넌 후 디딤돌에 적힌 숫자는 아래 그림과 같습니다.
두 번째 친구도 아래 그림과 같이 징검다리를 건널 수 있습니다.

두 번째 친구가 징검다리를 건넌 후 디딤돌에 적힌 숫자는 아래 그림과 같습니다.
세 번째 친구도 아래 그림과 같이 징검다리를 건널 수 있습니다.

세 번째 친구가 징검다리를 건넌 후 디딤돌에 적힌 숫자는 아래 그림과 같습니다.
네 번째 친구가 징검다리를 건너려면, 세 번째 디딤돌에서 일곱 번째 디딤돌로 네 칸을 건너뛰어야 합니다. 하지만 k = 3 이므로 건너뛸 수 없습니다.

따라서 최대 3명이 디딤돌을 모두 건널 수 있습니다.
풀이 방법
- 문제에서 읽으면 알겠지만, n명이 지나갔을 때, 각 돌은 value - n 만큼 값이 깎이며 어느샌가 0인 돌들이 많이 생길 것이다. 이때 k개만큼 돌만큼의 간격이 생기면 못 지나가는 것!
- 그러나 n를 1부터 탐색하기엔 최대 200,000 크기의 배열을 계속 순차적으로 탐색하면 무조건 시간초과가 날것
- 따라서, 우리는 돌들의 최소와 최대를 구해서 파라메트릭 서치를 실시하면 된다.
파라메트릭 서치를 하는 이유?
- 완전탐색의 경우, 배열의 크기가 크면 클수록 n의 제곱 배수처럼 기하급수적으로 커짐
- 파라메트릭서치의 경우, 배열의 크기가 크더라도 log 배수로 증가함.

- 완전탐색으로는 약 8번의 탐색 후에 8을 찾을 수 있다.

- 이분 탐색의 경우, 약 3번의 연산후에 바로 8을 찾아낼 수 있다.
물론 초반 값을 찾는다면 (2~3정도...) 완전탐색이 유리한 부분도 있겠지만, 수가 커지면 커질수록 이분탐색의 연산수가 급격하게 줄어들게 된다.
-> 예시로, 1~1000사이에서 975를 찾는 연산은 완전탐색은 975회, 이분탐색은 약 8회 정도의 연산만 수행이 된다.
풀이 코드
class Solution {
public int solution(int[] stones, int k) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
//순회하면서 최소, 최대 찾기
for(int stone: stones){
min = Math.min(min, stone);
max = Math.max(max, stone);
}
//이분..탐색?
while(min < max){
//중간 값
int mid = (min + max + 1) / 2;
if(isPossible(stones, mid, k)){
min = mid;
}
else {
max = mid - 1;
}
}
return max;
}
//해당 값의 돌로도 이동이 가능한지 테스트
public boolean isPossible(int[] stones, int mid, int k){
int count = 0;
for(int stone: stones){
if(stone - mid < 0){
count++;
}
else {
count = 0;
}
//카운트가 넘어가면 못건너가므로...
if(count >= k){
return false;
}
}
return true;
}
}
결과

회고
- 뭘 써야할지 유추하는 게 제일 어려운듯한