컨텐츠 검색
[프로그래머스/Day 16] 로또의 최고 순위와 최저 순위 - all_of, Lookup Table, STL

2026. 1. 23. 09:53알고리즘

오늘은 프로그래머스 '로또의 최고 순위와 최저 순위' 문제를 풀면서 시도했던 다양한 접근 방식과,

테스트 케이스를 통해 예외 처리를 확실히 잡았던 경험을 공유하고자 합니다.

1. 문제 개요

이 문제는 지워진 로또 번호를 0으로 가정했을 때, 당첨 가능한 최고 순위와 최저 순위를 구하는 문제입니다.

핵심은 0이 모두 당첨 번호라고 가정할 때(최고 순위)와 모두 꽝이라고 가정할 때(최저 순위)를 계산하는 것입니다.


2. 첫 번째 접근: all_of 활용

처음에는 흐름대로 로직을 작성했습니다. 0인 경우와 숫자가 일치하는 경우를 분기 처리하여 계산하는 방식입니다.

특히, 입력값이 모두 0인 특수한 케이스([0, 0, 0, 0, 0, 0])를 빠르게 처리하기 위해 <algorithm> 헤더의 all_of 함수를 먼저 사용했습니다.

// 1. lottos의 모든 값이 0이라면 result는 [1, 6] 반환한다.
// 2. lottos가 win_nums에 일치하는게 몇개인지 확인한다.
// - lottos의 원소가 0이 아니라면 win_nums의 원소와 비교: 최저 순위 구하기
// - lottos의 원소가 0이라면 0 개수로 카운트
#include <algorithm>
#include <vector>

using namespace std;

vector<int> solution(vector<int> lottos, vector<int> win_nums) {
    // 1. lottos의 모든 값이 0이라면 result는 [1, 6] 반환한다.
    if (all_of(lottos.begin(), lottos.end(), [](int n){return n == 0;})) return {1, 6};

    int place = 7;
    int zeroCount = 0;
    // 2. lottos가 win_nums에 일치하는게 몇개인지 확인한다.
    for(int i = 0; i < 6; ++i)
    {
        int current = lottos[i];
        // - lottos의 원소가 0이 아니라면 win_nums의 원소와 비교: 최저 순위 구하기
        if(current != 0)
        {
            if(find(win_nums.begin(), win_nums.end(), current) != win_nums.end())
            {
                --place;
            }
        }
        // - lottos의 원소가 0이라면 0 개수로 카운트
        else
        {
            ++zeroCount;
        }
    }

    if(place == 7) place = 6;

    return {place - zeroCount, place};
}

[테스트 케이스의 중요성] 문제를 풀던 중, 다음과 같은 테스트 케이스를 추가하여 검증을 진행했습니다.

  • Input: lottos: [1, 2, 3, 4, 5, 6], win_nums: [7, 8, 9, 10, 11, 12] (모두 꽝인 경우)
  • Expected Output: [6, 6]

이 케이스 덕분에 place 변수가 초기값 7로 남아있을 때 이를 6으로 바꿔주는 예외 처리(if(place == 7) place = 6;)가 반드시 필요하다는 것을 놓치지 않을 수 있었습니다. 만약 이 처리가 없었다면 [7, 7]이라는 잘못된 결과가 나왔을 것입니다.


3. 두 번째 접근: Lookup Table 활용

첫 번째 코드는 로직이 명확하지만, 순위를 계산하기 위해 place 변수를 7에서 깎아내리거나 마지막에 예외 처리를 해야 하는 번거로움이 있었습니다. 이를 개선하기 위해 Lookup Table 방식을 적용해 보았습니다.

#include <vector>
#include <algorithm>

using namespace std;

vector<int> solution(vector<int> lottos, vector<int> win_nums) {
    // 인덱스 = 맞춘 개수, 값 = 등수
    // 0개 맞춤 -> 6등, 1개 맞춤 -> 6등, ... 6개 맞춤 -> 1등
    int rank[] = {6, 6, 5, 4, 3, 2, 1};

    int zero_count = 0;
    int match_count = 0;

    for (int num : lottos) {
        if (num == 0) {
            zero_count++;
        } else {
            if (find(win_nums.begin(), win_nums.end(), num) != win_nums.end()) {
                match_count++;
            }
        }
    }

    // 배열 인덱스로 접근하여 예외 처리(if문) 제거
    return {rank[match_count + zero_count], rank[match_count]};
}

이 방식의 장점은 if (place == 7)과 같은 예외 처리가 사라진다는 점입니다.

match_count가 0이어도 rank[0]은 6이므로 자연스럽게 해결됩니다.

코드가 훨씬 간결하고 데이터 중심적으로 변했습니다.


4. 세 번째 접근: STL

마지막으로, 반복문을 직접 돌리는 대신 C++ STL의 기능을 적극적으로 활용하여 가독성을 높이는 방식도 고려해보았습니다.

#include <vector>
#include <algorithm>

using namespace std;

vector<int> solution(vector<int> lottos, vector<int> win_nums) {
    // 0의 개수 카운트
    int zero_cnt = count(lottos.begin(), lottos.end(), 0);

    // 맞은 개수 카운트
    int match_cnt = 0;
    for (int win : win_nums) {
        if (find(lottos.begin(), lottos.end(), win) != lottos.end()) {
            match_cnt++;
        }
    }

    // 등수 계산 람다 함수
    auto get_rank = [](int cnt) {
        return cnt < 2 ? 6 : 7 - cnt;
    };

    return {get_rank(match_cnt + zero_cnt), get_rank(match_cnt)};
}

std::count를 사용하여 0의 개수를 세고, 등수 계산 로직을 람다 함수로 분리하였습니다.


5. 정리

세 가지 방식을 비교해 본 결과, 알고리즘 문제 풀이에서는 두 번째 접근(Lookup Table)이 가장 효율적이고 실수를 줄여주는 방법이라고 생각합니다.

특히 이번 풀이에서는 "아예 하나도 맞지 않은 경우"([1, 2, 3, 4, 5, 6] vs [7, 8, 9, 10, 11, 12]) 를 테스트 케이스로 추가하여 검증했던 것이 주효했습니다.

엣지 케이스를 미리 생각하고 코드를 작성하는 습관이 버그를 막는 가장 좋은 방법임을 다시 한번 느꼈습니다.