[Programmers/ level3] 징검다리 건너기🙄

3 분 소요


👩문제

[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]

카카오 초등학교의 “니니즈 친구들”이 “라이언” 선생님과 함께 가을 소풍을 가는 중에 징검다리가 있는 개울을 만나서 건너편으로 건너려고 합니다. “라이언” 선생님은 “니니즈 친구들”이 무사히 징검다리를 건널 수 있도록 다음과 같이 규칙을 만들었습니다.

징검다리는 일렬로 놓여 있고 각 징검다리의 디딤돌에는 모두 숫자가 적혀 있으며 디딤돌의 숫자는 한 번 밟을 때마다 1씩 줄어듭니다. 디딤돌의 숫자가 0이 되면 더 이상 밟을 수 없으며 이때는 그 다음 디딤돌로 한번에 여러 칸을 건너 뛸 수 있습니다. 단, 다음으로 밟을 수 있는 디딤돌이 여러 개인 경우 무조건 가장 가까운 디딤돌로만 건너뛸 수 있습니다. “니니즈 친구들”은 개울의 왼쪽에 있으며, 개울의 오른쪽 건너편에 도착해야 징검다리를 건넌 것으로 인정합니다. “니니즈 친구들”은 한 번에 한 명씩 징검다리를 건너야 하며, 한 친구가 징검다리를 모두 건넌 후에 그 다음 친구가 건너기 시작합니다.

디딤돌에 적힌 숫자가 순서대로 담긴 배열 stones와 한 번에 건너뛸 수 있는 디딤돌의 최대 칸수 k가 매개변수로 주어질 때, 최대 몇 명까지 징검다리를 건널 수 있는지 return 하도록 solution 함수를 완성해주세요.

👩제한사항

  • 징검다리를 건너야 하는 니니즈 친구들의 수는 무제한 이라고 간주합니다.
  • stones 배열의 크기는 1 이상 200,000 이하입니다.
  • stones 배열 각 원소들의 값은 1 이상 200,000,000 이하인 자연수입니다.
  • k는 1 이상 stones의 길이 이하인 자연수입니다.


👩입출력 예

stones k result
[2, 4, 5, 3, 2, 1, 4, 2, 5, 1] 3 3


🙋‍♀️접근 방법

우선 효율성 다 따지지 말고 가장 떠올리기 쉬운 방식부터 생각해보자!

☝️ 가장 쉽게 생각할 수 있는 방법

  • 한 사람이 건널 때마다 배열의 값들을 1씩 감소시킨다.
  • 더 이상 건너지 못할 때까지 반복하여 최종 사람 수를 구한다.

🙄 그렇다면 이렇게 했을 때 시간복잡도는 어떻게 될까?

  • 배열의 최대 크기 * 배열 원소의 최대값 = 20만 * 2억 -> 해당 방식으로는 효율성을 통과하지 못하게 된다.


그 다음으로 내가 시도한 방식은 다음과 같다. 코드를 통해서 그 방식을 살펴보자!

✌️ 살짝 나아진 효율성!

def solution(stones, k):
    m = 200000000
    size = len(stones)
    for i in range(size): #stones[i]명의 사람이 건널 경우
        tmp = 1
        if stones[i] >= m: 
            continue
            
        for l in range(i-1, max(-1, i-k), -1): #i 이전 징검다리에서 0이 되는 돌의 수
            if stones[i] >= stones[l]:
                tmp += 1
            else:
                break
                
        if len(stones)-i-1+tmp < k: # i 이후 모든 징검다리가 0이 되더라도 k개가 안되는 경우
            continue
            
        for j in range(i+1, min(i+k, size)): #i 이후 징검다리에서 0이 되는 돌의 수
            if stones[i] >= stones[j]:
                tmp += 1
            else:
                break

        if tmp >= k: #stones[i]명이 건널 경우 연속해서 뛰어야 하는 징검다리수가 k보다 크거나 같을 경우
            m = min(m, stones[i])
    return m

즉, 정리하면 다음과 같다.

  • 배열의 값 = 건너는 사람의 수라고 가정!!
  • 배열을 돌며 해당 숫자의 사람이 건널 경우, 모두 건널 수 있는 지를 판단한다.
    • 배열 i번째를 확인할 경우
      • 건너는 사람의 수 = stones[i]
      • i에서 +/-k 사이의 값들을 확인해 stones[i]보다 작은 값인지를 확인!
        • 작을 경우, stones[i]명이 다리를 건넌다면 그 돌의 값은 0 이하가 된다.
      • 연속해서 0 이하가 되는 징검다리 수를 계산해 k와 비교하여 최소 m 값을 계산한다.

🙄 그렇다면 위 알고리즘의 시간복잡도는 어떻게 될까?

  • 배열의 최대 크키 * k의 최대 크기 = 20만 * 20만

물론 위 1번 알고리즘보다는 시간복잡도가 나아졌지만 효율성 두 개를 통과하지 못한다.


👌해답은 이분탐색!

처음 나는 이 문제에 어떻게 이분탐색을 적용하지 싶었다. 이분탐색을 적용하기 위해서는 배열이 정렬되어 있어야 하는데, 그렇게 된다면 문제 자체가 망가지기 때문이다. 내가 잘못 생각했던 부분은 무조건 배열에서 이분탐색을 진행해야 한다고 생각했던 점이다!!

⭐우리가 구할 것은 몇 명이 건널 수 있는지다!⭐ 그렇다! 우리는 건널 수 있는 사람의 수의 범위에서 이분탐색을 진행하면 된다!

🙄 시간복잡도?

  • 사람 수 범위 = stones 값 범위
  • log(2억) = 27-28


그렇다면 이제 코드를 작성해보자! 이분탐색을 적용한다면 정말 쉽게 문제가 해결된다!


🙋‍♀️알고리즘

def solution(stones, k):
    start = 1
    end = max(stones)
    
    while start <= end:
        mid = (start+end)//2 # mid번째 사람이 건널 수 있는지를 판단하게 된다.
        count = 0
        for i in stones:
            if count < k:
                if i-mid <= 0:
                    count += 1
                else:# 0이하가 아닌 징검다리를 만났으므로 count를 초기화한다.
                    count = 0 
            else:
                break
        if count < k:# mid번째 사람이 건널 수 있으므로 mid 이상의 사람이 건널 수 있는지를 판단한다.
            start = mid+1
        else: # mid번째 사람이 건너지 못하므로 mid 이하에서 건널 수 있는 최대 인원을 구한다.
            end = mid-1
            result = mid
    return result

🙇‍♀️풀이에 부족한 부분이 있다면 말씀해주세요! 감사합니다!


📃 참고

댓글남기기