[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]
카카오 초등학교의 "니니즈 친구들"이 "라이언" 선생님과 함께 가을 소풍을 가는 중에 징검다리가 있는 개울을 만나서 건너편으로 건너려고 합니다. "라이언" 선생님은 "니니즈 친구들"이 무사히 징검다리를 건널 수 있도록 다음과 같이 규칙을 만들었습니다.
징검다리는 일렬로 놓여 있고 각 징검다리의 디딤돌에는 모두 숫자가 적혀 있으며 디딤돌의 숫자는 한 번 밟을 때마다 1씩 줄어듭니다.
디딤돌의 숫자가 0이 되면 더 이상 밟을 수 없으며 이때는 그 다음 디딤돌로 한번에 여러 칸을 건너 뛸 수 있습니다.
단, 다음으로 밟을 수 있는 디딤돌이 여러 개인 경우 무조건 가장 가까운 디딤돌로만 건너뛸 수 있습니다.
"니니즈 친구들"은 개울의 왼쪽에 있으며, 개울의 오른쪽 건너편에 도착해야 징검다리를 건넌 것으로 인정합니다.
"니니즈 친구들"은 한 번에 한 명씩 징검다리를 건너야 하며, 한 친구가 징검다리를 모두 건넌 후에 그 다음 친구가 건너기 시작합니다.
디딤돌에 적힌 숫자가 순서대로 담긴 배열 stones와 한 번에 건너뛸 수 있는 디딤돌의 최대 칸수 k가 매개변수로 주어질 때, 최대 몇 명까지 징검다리를 건널 수 있는지 return 하도록 solution 함수를 완성해주세요.
[제한사항]
징검다리를 건너야 하는 니니즈 친구들의 수는 무제한 이라고 간주합니다.
stones 배열의 크기는1
이상200,000
이하입니다.
stones 배열 각 원소들의 값은1
이상200,000,000
이하인 자연수입니다.
k는1
이상stones의 길이
이하인 자연수입니다.
이 문제의 최대 N값은 2억이기 때문에 완전 탐색을 한다면 엄마 나 군대 갔다올테니 컴퓨터 끄지마를 시전하실 수도 있습니다.
따라서 저희는 이분 탐색
을 시도해볼 수 있습니다.
풀이를 보시기 전에 이분 탐색에 대한 충분한 이해가 필요합니다.
이분탐색, upper bound, lower bound에 대해 숙지하시면 이해하는데 많은 도움이 되실거에요.
이 문제의 풀이 로직은 다음과 같습니다.
min = 0, max = 200_000_000
반복문 시작: min < max
mid = (min + max) / 2
만약, mid명의 인원으로 모든 징검다리를 건널 수 있다면,
min = mid + 1 (더 많은 인원을 투입해본다.)
만약 건널 수 없다면,
max = mid (더 적은 인원을 투입해본다.)
반복문 종료
정답 반환: min - 1
정답으로 min - 1을 한 이유는 upper bound
를 이용하였기 때문입니다.
upper bound
의 특성은 이분 탐색을 통하여 목표값 바로 다음으로 큰 수를 찾아내는 알고리즘입니다. 따라서, 위 로직은 정답 값보다 1 만큼 더 많은 값을 리턴
하기 때문에, -1 처리를 해주는겁니다.
앞서 말씀드린 로직을 구현하기 위해서는 특정 인원(mid)이 징검다리를 건널 수 있는지 확인해야합니다. 이 로직도 효율성에 신경을 써주어야합니다. 만약 한 명씩 징검다리를 건너도록 프로그래밍을 하면 최대 2억번의 연산을 하기 때문입니다.
문제에서 최대 k
거리 만큼 점프를 할 수 있다고 정의되어 있기 때문에, 전체 인원이 밟을 수 없는 연속되는 돌을 카운트 해주어야합니다.
private fun crossBridge(k: Int, mid: Int, bridge: IntArray): Boolean {
// mid 만큼의 인원이 건널 수 있는지 확인한다.
// 한 번에 뛸 수 있는 징검다리가 k를 초과한다면 건널 수 없다.
var cntZero = 0 // 밟을 수 없는 연속되는 돌의 개수
for (item in bridge) {
if (item - mid < 0) {
cntZero++ // 모든 사람이 돌을 밟을 수 없음
} else {
cntZero = 0 // 모든 사람이 이 돌을 밟을 수 있음
}
// 밟을 수 없는 디딤돌이 연속해서 k개가 된다면, 건널 수 없다.
if (cntZero == k) {
return false
}
}
return true
}
package Kakao_Internship_2019.징검다리_건너기
class Solution {
fun solution(stones: IntArray, k: Int): Int {
var min = 0
var max = 200_000_000
// 구하고자 하는 값은 `친구들의 최대` 이기 때문에 upper bound 를 고려해볼 수 있다.
while (min < max) {
val mid = (min + max) / 2
if (crossBridge(k, mid, stones)) {
// 만약 건널 수 있으면 mid값을 더 높여봐야한다.
min = mid + 1
} else {
max = mid
}
}
return min - 1
}
private fun crossBridge(k: Int, mid: Int, bridge: IntArray): Boolean {
// mid 만큼의 인원이 건널 수 있는지 확인한다.
// 한 번에 뛸 수 있는 징검다리가 k를 초과한다면 건널 수 없다.
var cntZero = 0 // 밟을 수 없는 연속되는 돌의 개수
for (item in bridge) {
if (item - mid < 0) {
cntZero++ // 모든 사람이 돌을 밟을 수 없음
} else {
cntZero = 0 // 모든 사람이 이 돌을 밟을 수 있음
}
// 밟을 수 없는 디딤돌이 연속해서 k개가 된다면, 건널 수 없다.
if (cntZero == k) {
return false
}
}
return true
}
}