본문 바로가기
알고리즘 문제 풀이/프로그래머스

프로그래머스 풍선 터트리기 c++ (dp) 2022-06-18 코틀린 추가

by 옹구스투스 2021. 7. 17.
반응형

문제 출처 : https://programmers.co.kr/learn/courses/30/lessons/68646

 

코딩테스트 연습 - 풍선 터트리기

[-16,27,65,-2,58,-92,-71,-68,-61,-33] 6

programmers.co.kr

문제 설명

일렬로 나열된 n개의 풍선이 있습니다. 모든 풍선에는 서로 다른 숫자가 써져 있습니다. 당신은 다음 과정을 반복하면서 풍선들을 단 1개만 남을 때까지 계속 터트리려고 합니다.

  1. 임의의 인접한 두 풍선을 고른 뒤, 두 풍선 중 하나를 터트립니다.
  2. 터진 풍선으로 인해 풍선들 사이에 빈 공간이 생겼다면, 빈 공간이 없도록 풍선들을 중앙으로 밀착시킵니다.

여기서 조건이 있습니다. 인접한 두 풍선 중에서 번호가 더 작은 풍선을 터트리는 행위는 최대 1번만 할 수 있습니다. 즉, 어떤 시점에서 인접한 두 풍선 중 번호가 더 작은 풍선을 터트렸다면, 그 이후에는 인접한 두 풍선을 고른 뒤 번호가 더 큰 풍선만을 터트릴 수 있습니다.

당신은 어떤 풍선이 최후까지 남을 수 있는지 알아보고 싶습니다. 위에 서술된 조건대로 풍선을 터트리다 보면, 어떤 풍선은 최후까지 남을 수도 있지만, 어떤 풍선은 무슨 수를 쓰더라도 마지막까지 남기는 것이 불가능할 수도 있습니다.

일렬로 나열된 풍선들의 번호가 담긴 배열 a가 주어집니다. 위에 서술된 규칙대로 풍선들을 1개만 남을 때까지 터트렸을 때 최후까지 남기는 것이 가능한 풍선들의 개수를 return 하도록 solution 함수를 완성해주세요.


제한 사항

  • a의 길이는 1 이상 1,000,000 이하입니다.
    • a[i]는 i+1 번째 풍선에 써진 숫자를 의미합니다.
    • a의 모든 수는 -1,000,000,000 이상 1,000,000,000 이하인 정수입니다.
    • a의 모든 수는 서로 다릅니다.

입출력 예


입출력 예 설명

입출력 예 #1

  • 첫 번째 풍선(9가 써진 풍선)을 최후까지 남기는 방법은 다음과 같습니다.
    1. [9, -1, -5] 에서 -1, -5가 써진 풍선을 고른 뒤, -1이 써진 풍선(번호가 더 큰 것)을 터트립니다.
    2. [9, -5] 에서 9, -5가 써진 풍선을 고른 뒤, -5가 써진 풍선(번호가 더 작은 것)을 터트립니다.
  • 두 번째 풍선(-1이 써진 풍선)을 최후까지 남기는 방법은 다음과 같습니다.
    1. [9, -1, -5] 에서 9, -1이 써진 풍선을 고른 뒤, 9가 써진 풍선(번호가 더 큰 것)을 터트립니다.
    2. [-1, -5] 에서 -1, -5가 써진 풍선을 고른 뒤, -5가 써진 풍선(번호가 더 작은 것)을 터트립니다.
  • 세 번째 풍선(-5가 써진 풍선)을 최후까지 남기는 방법은 다음과 같습니다.
    1. [9, -1, -5] 에서 9, -1이 써진 풍선을 고른 뒤, 9가 써진 풍선(번호가 더 큰 것)을 터트립니다.
    2. [-1, -5] 에서 -1, -5가 써진 풍선을 고른 뒤, -1이 써진 풍선(번호가 더 큰 것)을 터트립니다.
  • 3개의 풍선이 최후까지 남을 수 있으므로, 3을 return 해야 합니다.

입출력 예 #2

  • 최후까지 남을 수 있는 풍선은 -16, -92, -71, -68, -61, -33이 써진 풍선으로 모두 6개입니다.

 

풀이

'프로그래머스 / 월간 코드 챌린지 시즌1 / 풍선 터트리기'로 분류되어 있는 문제이다.

 

우선 검사하는 풍선을 최후까지 남기려면 어떠한 조건이어야 하는지 찾아야 한다.

인접한 두 개의 풍선 중에서 항상 큰 풍선이 터지기 때문에, 만약 i번째 풍선이 최후까지 남으려면,

i번째 풍선 왼쪽에 있는 0~(i-1)번째 풍선들이 모두 i번째 풍선보다 커야 하고,

i번째 풍선 오른쪽에 있는 (i+1)~end번째 풍선들도 모두 i번째 풍선보다 커야 한다.

하지만 문제에서, 번호가 더 작은 풍선을 터트릴 수 있는 기회를 한 번 주기 때문에,

왼쪽에 있는 풍선들이나, 오른쪽에 있는 풍선들, 둘 중 한 방향의 풍선 그룹은 i번째 풍선보다 작아도 된다.

따라서 점화식은 아래와 같다.

if(a[i]>(a[i-1]~a[0]) || a[i] > (a[i+1] ~a[end])) answer++;

 

하지만 완전 탐색으로 a[i]와 i 왼쪽에 있는 풍선들의 그룹을 비교하고, a[i]와 i 오른쪽에 있는 풍선들의 그룹을 비교하면,

주어진 배열의 크기가 1,000,000이기 때문에 시간 초과가 뜬다.

 

i의 왼쪽에 있는 풍선들의 그룹이 모두 i보다 크려면, a[i-1]~a[0]의 최솟값이 a[i]보다 커야 한다. (오른쪽도 마찬가지)

왼쪽에 있는 그룹들의 최솟값만 알아도 된다면, 우선순위큐를 이용해 선행시간으로 풀 수 있지만,

왼쪽 그룹, 오른쪽 그룹 두 개의 최솟값을 알아야 하기 때문에, 동적계획법의 메모이제이션을 이용한다.

 

풍선들의 배열 a가{ 2, 3, 1, 5} 라고 하고, 왼쪽과 오른쪽 풍선 그룹들의 최솟값을 저장할 배열을

left_min[4], right_min[4]라고 하면 각각의 최솟값은 아래와 같고,

left_min[0] = 2, left_min[1] = 2, left_min[2] = 1, left_min[3] = 1  

right_min[3] = 5, right_min[2] = 1, right_min[1] = 1, right_min[0] = 1

 

a[2] 풍선이 최후까지 남는지 확인하려면,

  left_min[1]>a[2] || a[2]<right_min[3]이면 된다.

 

코드1

#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#define MAX 1000000

using namespace std;
int left_min[MAX],right_min[MAX];

int solution(vector<int> a) {
    int answer = 0;
    
    //왼쪽부터 최솟값 저장
    left_min[0]=a[0];
    for(int i=1; i<a.size()-1;i++){
        left_min[i] = min(left_min[i-1],a[i]); 
    }
    
    //오른쪽부터 최솟값 저장
    right_min[a.size()-1]=a[a.size()-1];
    for(int i=a.size()-2;i>0;i--){
        right_min[i] = min(right_min[i+1],a[i]); 
    }
    
    //i번째 풍선을 검사할 때, i의 왼쪽 풍선들의 최솟값, i의 오른쪽 풍선들의 최솟값 둘 중
    //하나라도 i번째 풍선의 값보다 작으면 된다.
    for(int i=0; i<a.size();i++){
        if(i==0||i==a.size()-1){
            answer++;
            continue;
        }
        if(a[i]<left_min[i-1] || a[i]<right_min[i+1])
            answer++;
    }
    
    return answer;
}

코드2

class Solution {
    fun solution(a: IntArray): Int {
        var answer: Int = 0
        //preset
        val leftMin = IntArray(a.size).apply{ this[0] = a[0] }
        val rightMin = IntArray(a.size).apply{ this[a.size-1] = a[a.lastIndex] }
        for(i in 1 until a.size){
            leftMin[i] = a[i].coerceAtMost(leftMin[i-1])
        }
        for(i in a.size-2 downTo 0){
            rightMin[i] = a[i].coerceAtMost(rightMin[i+1])
        }
        //solve
        for(i in a.indices){
            if(i==0 || i ==a.size-1){
                answer++
            }
            else{
                if(a[i] < leftMin[i-1] || a[i] <rightMin[i+1]){
                    answer++
                }
            }
        }

        return answer
    }
}
반응형

댓글