728x90

문제 설명

어느 학교에 페인트가 칠해진 길이가 n미터인 벽이 있습니다. 벽에 동아리 · 학회 홍보나 회사 채용 공고 포스터 등을 게시하기 위해 테이프로 붙였다가 철거할 때 떼는 일이 많고 그 과정에서 페인트가 벗겨지곤 합니다. 페인트가 벗겨진 벽이 보기 흉해져 학교는 벽에 페인트를 덧칠하기로 했습니다.

넓은 벽 전체에 페인트를 새로 칠하는 대신, 구역을 나누어 일부만 페인트를 새로 칠 함으로써 예산을 아끼려 합니다. 이를 위해 벽을 1미터 길이의 구역 n개로 나누고, 각 구역에 왼쪽부터 순서대로 1번부터 n번까지 번호를 붙였습니다. 그리고 페인트를 다시 칠해야 할 구역들을 정했습니다.

벽에 페인트를 칠하는 롤러의 길이는 m미터이고, 롤러로 벽에 페인트를 한 번 칠하는 규칙은 다음과 같습니다.

  • 롤러가 벽에서 벗어나면 안 됩니다.
  • 구역의 일부분만 포함되도록 칠하면 안 됩니다.

즉, 롤러의 좌우측 끝을 구역의 경계선 혹은 벽의 좌우측 끝부분에 맞춘 후 롤러를 위아래로 움직이면서 벽을 칠합니다. 현재 페인트를 칠하는 구역들을 완전히 칠한 후 벽에서 롤러를 떼며, 이를 벽을 한 번 칠했다고 정의합니다.

한 구역에 페인트를 여러 번 칠해도 되고 다시 칠해야 할 구역이 아닌 곳에 페인트를 칠해도 되지만 다시 칠하기로 정한 구역은 적어도 한 번 페인트칠을 해야 합니다. 예산을 아끼기 위해 다시 칠할 구역을 정했듯 마찬가지로 롤러로 페인트칠을 하는 횟수를 최소화하려고 합니다.

정수 n, m과 다시 페인트를 칠하기로 정한 구역들의 번호가 담긴 정수 배열 section이 매개변수로 주어질 때 롤러로 페인트칠해야 하는 최소 횟수를 return 하는 solution 함수를 작성해 주세요.


제한사항
  • 1 ≤ m  n ≤ 100,000
  • 1 ≤ section의 길이 ≤ n
    • 1 ≤ section의 원소 ≤ n
    • section의 원소는 페인트를 다시 칠해야 하는 구역의 번호입니다.
    • section에서 같은 원소가 두 번 이상 나타나지 않습니다.
    • section의 원소는 오름차순으로 정렬되어 있습니다.
입출력 예
입출력 예 설명

입출력 예 #1

  • 예제 1번은 2, 3, 6번 영역에 페인트를 다시 칠해야 합니다. 롤러의 길이가 4미터이므로 한 번의 페인트칠에 연속된 4개의 구역을 칠할 수 있습니다. 처음에 3, 4, 5, 6번 영역에 페인트칠을 하면 칠해야 할 곳으로 2번 구역만 남고 1, 2, 3, 4번 구역에 페인트칠을 하면 2번 만에 다시 칠해야 할 곳에 모두 페인트칠을 할 수 있습니다.2번보다 적은 횟수로 2, 3, 6번 영역에 페인트를 덧칠하는 방법은 없습니다. 따라서 최소 횟수인 2를 return 합니다.

입출력 예 #2

  • 예제 2번은 1, 3번 영역에 페인트를 다시 칠해야 합니다. 롤러의 길이가 4미터이므로 한 번의 페인트칠에 연속된 4개의 구역을 칠할 수 있고 1, 2, 3, 4번 영역에 페인트칠을 하면 한 번에 1, 3번 영역을 모두 칠할 수 있습니다.따라서 최소 횟수인 1을 return 합니다.

입출력 예 #3

  • 예제 3번은 모든 구역에 페인트칠을 해야 합니다. 롤러의 길이가 1미터이므로 한 번에 한 구역밖에 칠할 수 없습니다. 구역이 4개이므로 각 구역을 한 번씩만 칠하는 4번이 최소 횟수가 됩니다.따라서 4를 return 합니다.

문제는 이런 문제인데, 

class Solution {
    fun solution(n: Int, m: Int, section: IntArray): Int {
        var answer: Int = 0
        var count = 0
        for(i in section.first()..section.last() step m){
            if(i <= section.last()){
                count++
            }
        }
        answer = count
        return answer
    }
}

나의 풀이로는 정확성이 54.0 이 나왔다.  

예외가 뭐가 있을까 생각을 해봐도 테스트 케이스가 50개 였기 때문에 알기 쉽지 않아서 풀이를 찾아본 결과 fold를 이용해서 푸는 방법이 있었고,

 

fold는 이런 함수다. 

출처 -&nbsp;https://gold.gitbook.io/kotlin/collections/aggregate-operations/fold

 

초기값을 0 으로 셋팅하고 acc에 연산 값을 누적 1 + 2 + 3 + 4 + 5 // 15 

list.fold(1, {acc, next -> acc * next})) // 120 

 

fold를 사용한 풀이 

class Solution {
    fun solution(n: Int, m: Int, section: IntArray): Int {
        var answer: Int = 0
        var endPoint = 0
        answer = section.fold(0){count,area->
            if(endPoint<area){//endPoint 가 area보다 작으면 페인트 칠 할 공간이 남았다는 것
                endPoint = area + m - 1 // 2부터 4(m)만큼 페인트칠 하고 끝 지점은 5이기 때문에 -1
                count + 1
            }else{
                count
            }
        }
        return answer
    }
}

 

fold 는 왼쪽에서 오른쪽으로 연산을 하고 오른쪽에서 왼쪽으로 연산하려면 foldRight()를 사용하면 된다.

 

우선순위가 있는, 순서가 있는 연산에 대해서는 두개의 특징을 잘 알고 사용하는 것이 좋다.

우선순위가 끝쪽이 더 높다 하는 경우에는 foldRight 를, 우선순위가 왼쪽에 있는 값부터 해야한다 하는 경우 일반적인 fold 를 사용하면 좋다.

 

번외 - reduce , reduceRight 

fold 와 비슷 하지만, reduce 는 초기값을 받지 않고 첫번째 인덱스부터 시작한다.  

 -> 초기값을 정해서 누적 연산을 누적하는 것과 초기값 지정없이 첫 번째 인덱스를 초기값으로 지정해 연산을 누적하는

차이  

fun main() {
    val list = listOf(1,2,3,4,5)
    var result = 0
    result = list.fold(0){total,next->
        total + next * 2 // result -> 초기값 1 결과값 30 -> 0 + (1*2) + (2*2) + (3*2) + (4*2) + (5*2)
    }
    result = list.reduce{total , next->
        total + next * 2 // result -> 초기값은 첫번째 인덱스(1) 결과값 29 -> 1 + (2*2) + (3*2) + (4*2) + (5*2)  
    }
}

 

예시를 보면 fold 는 1부터(첫번째 인덱스) 2를 곱한 결과를 total 에 누적시켜 result 값이 30 이고,

reduce는 1을(첫번째 인덱스) 초기값으로 가져 2(두번째 인덱스) 부터 2를 곱해 total 에 누적시킨 결과 result 는 29가 나옴

 

 

 

 

문제를 풀다보면 풀이 방식이 고정되고 그 방식을 벗어나 다른 풀이 방식으로 접근하기가 쉽지 않은 것 같다. 그래서 알고리즘을 풀면서 다른 사람들의 풀이도 보게 되는데 항상 부족함을 느낀다.   

 

 

+ Recent posts