보석 쇼핑 - Swift

2021. 3. 9. 05:22Algorithm/프로그래머스

문제 설명

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

개발자 출신으로 세계 최고의 갑부가 된 어피치는 스트레스를 받을 때면 이를 풀기 위해 오프라인 매장에 쇼핑을 하러 가곤 합니다.
어피치는 쇼핑을 할 때면 매장 진열대의 특정 범위의 물건들을 모두 싹쓸이 구매하는 습관이 있습니다.
어느 날 스트레스를 풀기 위해 보석 매장에 쇼핑을 하러 간 어피치는 이전처럼 진열대의 특정 범위의 보석을 모두 구매하되 특별히 아래 목적을 달성하고 싶었습니다.
진열된 모든 종류의 보석을 적어도 1개 이상 포함하는 가장 짧은 구간을 찾아서 구매

예를 들어 아래 진열대는 4종류의 보석(RUBY, DIA, EMERALD, SAPPHIRE) 8개가 진열된 예시입니다.

진열대 번호 1 2 3 4 5 6 7 8
보석 이름 DIA RUBY RUBY DIA DIA EMERALD SAPPHIRE DIA

진열대의 3번부터 7번까지 5개의 보석을 구매하면 모든 종류의 보석을 적어도 하나 이상씩 포함하게 됩니다. 

진열대의 3, 4, 6, 7번의 보석만 구매하는 것은 중간에 특정 구간(5번)이 빠지게 되므로 어피치의 쇼핑 습관에 맞지 않습니다.

진열대 번호 순서대로 보석들의 이름이 저장된 배열 gems가 매개변수로 주어집니다. 이때 모든 보석을 하나 이상 포함하는 가장 짧은 구간을 찾아서 return 하도록 solution 함수를 완성해주세요.
가장 짧은 구간의 시작 진열대 번호와 끝 진열대 번호를 차례대로 배열에 담아서 return 하도록 하며, 만약 가장 짧은 구간이 여러 개라면 시작 진열대 번호가 가장 작은 구간을 return 합니다.

제한 사항

  • gems 배열의 크기는 1 이상 100,000 이하입니다.
    • gems 배열의 각 원소는 진열대에 나열된 보석을 나타냅니다.
    • gems 배열에는 1번 진열대부터 진열대 번호 순서대로 보석 이름이 차례대로 저장되어 있습니다.
    • gems 배열의 각 원소는 길이가 1 이상 10 이하인 알파벳 대문자로만 구성된 문자열입니다.

해결 방법

스스로 해결하기 매우 어려웠던 문제, 카카오 공식 풀이를 보고 해결할 수 있었다. 투 포인터 기법을 활용하여 왼쪽 한계선과 오른쪽 한계선을 정하는 방법을 활용한다.

Swift의 Dictionary 자료형을 사용하였으며 이는 양 포인터 범위 안에 존재하는 보석의 종류와 보석의 빈도수를 뜻하며, Key 값은 보석 이름, value는 보석의 빈도수로 정의하였다.

양 포인터는 주어진 조건에 따라 한 칸씩 전진하며, 포인터 중 하나라도 gems 배열의 범위를 벗어나게 되면 탐색은 종료된다.

왼쪽 포인터는 주어진 보석의 전체 종류 수와 현재 범위 안에 있는 보석의 종류 수가 같을 경우 왼쪽 포인터를 +1 증가시킨다.

그리고 오른쪽 포인터는 주어진 보석의 전체 종류 수가 현재 범위 안에 있는 보석의 종류 수 보다 많을 경우 오른쪽 포인터를 +1 증가시킨다.

이때 중요한 것은 현재 범위를 나타내는 Dictionary 자료형에서 포인터를 옮길 때마다 값을 갱신시켜주는 것이다. 왼쪽 포인터를 옮기는 경우 이전 위치에 있던 보석을 범위에서 지워줘야 하며, 오른쪽 포인터를 옮기는 경우 옮긴 위치에 해당하는 보석 값을 범위에 업데이트시켜 주어야 한다. 그리고 만약 빈도 수가 0이 되는 보석이 생긴다면, 그 보석을 꼭 지워줘야 한다.

import Foundation

func solution(_ gems:[String]) -> [Int] {
    
    // 전체 보석의 종류
    let gemCount = Set<String>(gems).count
    
    // 범위안에 있는 보석을 저장
    // (0,0) 범위에서 시작을 하기 때문에 초기값을 설정한다.
    var dic: [String:Int] = [gems[0]:1]
    // 최적의 양쪽 포인터를 저장하는 변수
    // 초기값은 최대 범위로 지정
    var answer = (start: 0, end: gems.count - 1)
    
    // gems 배열을 순회할 양쪽 포인터
    var lPoint = 0
    var rPoint = 0
    while (lPoint < gems.count) && (rPoint < gems.count) {
        
        let count = dic.count
        // 범위안에 있는 보석의 종류 수 와 전체 보석의 종류수가 같을 때
        if count == gemCount {
            
            // 현재 포인터가 가리키는 범위의 길이와 answer 변수에 저장된 양쪽 포인터의 범위의 길이를 비교
            // 더 짧은 범위 값으로 answer 변수를 업데이트 한다.
            if rPoint - lPoint < answer.end - answer.start {
                answer.start = lPoint
                answer.end = rPoint
            }
            // 두 값이 같은 경우에는 왼쪽 포인터의 값이 작을 때
            // answer 변수를 업데이트
            else if rPoint - lPoint == answer.end - answer.start {
                if lPoint < answer.start {
                    answer.start = lPoint
                    answer.end = rPoint
                }
            }
            
            if let value = dic[gems[lPoint]] {
                // lPoint에 해당하는 보석의 빈도 수가 한개일 경우
                if value - 1 == 0 {
                    // dictionary 에서 삭제
                    dic.removeValue(forKey: gems[lPoint])
                }
                // 그렇지 않은 경우
                else {
                    // value 값을 감소시켜 준다.
                    dic.updateValue(value - 1, forKey: gems[lPoint])
                }
            }
            // lPoint 위치를 한칸 증가 시킨다.
            lPoint += 1
        }
        // 그렇지 않을 경우
        else {
            // rPoint 위치를 한칸 증가 시키고
            rPoint += 1
            // rPoint가 gems의 배열 범위를 벗아나게 되면 그대로 진행
            if rPoint >= gems.count {
                continue
            }
            // rPoint에 해당하는 보석의 빈도 수를 업데이트
            if let value = dic[gems[rPoint]] {
                dic.updateValue(value + 1, forKey: gems[rPoint])
            }
            else {
                dic.updateValue(1, forKey: gems[rPoint])
            }
        }
    }
    return [answer.start + 1, answer.end + 1]
}

 

 

 

코딩테스트 연습 - 보석 쇼핑

["DIA", "RUBY", "RUBY", "DIA", "DIA", "EMERALD", "SAPPHIRE", "DIA"] [3, 7]

programmers.co.kr

* 풀이법을 참고한 곳입니다. 카카오 공식 블로그입니다.

 

2020 카카오 인턴십 for Tech developers 문제해설

2020년 카카오의 여름 인턴십이 시작 되었습니다.여름 인턴십의 첫번째 관문인 코딩 테스트가 2020년 5월 9일 오후 2시부터 6시까지 진행되었는데요, 온라인으로 진행되었기 때문에 코로나19로부터

tech.kakao.com

'Algorithm > 프로그래머스' 카테고리의 다른 글

기둥과 보 설치  (0) 2021.03.14
[1차] 캐시 - Swift  (0) 2021.03.09