[프로그래머스] 2021 KAKAO BLIND RECRUITMENT - 메뉴 리뉴얼

부리또의 댄스·2025년 4월 18일
0

프로그래머스

목록 보기
1/3
post-thumbnail

문제

문제 설명

레스토랑을 운영하던 스카피는 코로나19로 인한 불경기를 극복하고자 메뉴를 새로 구성하려고 고민하고 있습니다.
기존에는 단품으로만 제공하던 메뉴를 조합해서 코스요리 형태로 재구성해서 새로운 메뉴를 제공하기로 결정했습니다. 어떤 단품메뉴들을 조합해서 코스요리 메뉴로 구성하면 좋을 지 고민하던 "스카피"는 이전에 각 손님들이 주문할 때 가장 많이 함께 주문한 단품메뉴들을 코스요리 메뉴로 구성하기로 했습니다.
단, 코스요리 메뉴는 최소 2가지 이상의 단품메뉴로 구성하려고 합니다. 또한, 최소 2명 이상의 손님으로부터 주문된 단품메뉴 조합에 대해서만 코스요리 메뉴 후보에 포함하기로 했습니다.

예를 들어, 손님 6명이 주문한 단품메뉴들의 조합이 다음과 같다면,
(각 손님은 단품메뉴를 2개 이상 주문해야 하며, 각 단품메뉴는 A ~ Z의 알파벳 대문자로 표기합니다.)

손님 번호주문한 단품메뉴 조합
1번 손님A, B, C, F, G
2번 손님A, C
3번 손님C, D, E
4번 손님A, C, D, E
5번 손님B, C, F, G
6번 손님A, C, D, E, H

가장 많이 함께 주문된 단품메뉴 조합에 따라 "스카피"가 만들게 될 코스요리 메뉴 구성 후보는 다음과 같습니다.

코스 종류메뉴 구성설명
요리 2개 코스A, C1번, 2번, 4번, 6번 손님으로부터 총 4번 주문됐습니다.
요리 3개 코스C, D, E3번, 4번, 6번 손님으로부터 총 3번 주문됐습니다.
요리 4개 코스B, C, F, G1번, 5번 손님으로부터 총 2번 주문됐습니다.
요리 4개 코스A, C, D, E4번, 6번 손님으로부터 총 2번 주문됐습니다.

문제

각 손님들이 주문한 단품메뉴들이 문자열 형식으로 담긴 배열 orders, "스카피"가 추가하고 싶어하는 코스요리를 구성하는 단품메뉴들의 갯수가 담긴 배열 course가 매개변수로 주어질 때, "스카피"가 새로 추가하게 될 코스요리의 메뉴 구성을 문자열 형태로 배열에 담아 return 하도록 solution 함수를 완성해 주세요.

제한사항

  • orders 배열의 크기는 2 이상 20 이하입니다.
  • orders 배열의 각 원소는 크기가 2 이상 10 이하인 문자열입니다.
    • 각 문자열은 알파벳 대문자로만 이루어져 있습니다.
    • 각 문자열에는 같은 알파벳이 중복해서 들어있지 않습니다.
  • course 배열의 크기는 1 이상 10 이하입니다.
    • course 배열의 각 원소는 2 이상 10 이하인 자연수가 오름차순으로 정렬되어 있습니다.
    • course 배열에는 같은 값이 중복해서 들어있지 않습니다.
  • 정답은 각 코스요리 메뉴의 구성을 문자열 형식으로 배열에 담아 사전 순으로 오름차순 정렬해서 return 해주세요.
    • 배열의 각 원소에 저장된 문자열 또한 알파벳 오름차순으로 정렬되어야 합니다.
    • 만약 가장 많이 함께 주문된 메뉴 구성이 여러 개라면, 모두 배열에 담아 return 하면 됩니다.
    • orders와 course 매개변수는 return 하는 배열의 길이가 1 이상이 되도록 주어집니다.

입출력 예

orderscourseresult
["ABCFG", "AC", "CDE", "ACDE", "BCFG", "ACDEH"][2,3,4]["AC", "ACDE", "BCFG", "CDE"]
["ABCDE", "AB", "CD", "ADE", "XYZ", "XYZ", "ACD"][2,3,5]["ACD", "AD", "ADE", "CD", "XYZ"]
["XYZ", "XWY", "WXA"][2,3,4]["WX", "XY"]

풀이

풀이과정

구현 너무 빡세다.. 그리고 문제도 너무 길어서 각 코스요리 개수 당 '가장 많이' 주문된 조합을 선택해야 한다는 것을 간과해서 또 오래걸렸다..
그래도 dp나 DFS, BFS와 같은 것보다는 그냥 뇌빼고(?) 문제대로만 차근차근 하면 돼서 좋기도 했다!

프로그래머스의 문제들은 꼭 꼼꼼히 잘 읽어야겠다고 생각했다.

최종 코드

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>

using namespace std;

// 문자열 조합 생성 (재귀를 이용한 백트래킹)
void combine(const string& s, int start, string path, int r, vector<pair<string, int>>& comb) {
    if (path.length() == r) {
        auto it = find_if(comb.begin(), comb.end(), [&](const pair<string, int>& p) {
            return p.first == path;
        });

        if (it == comb.end()) {
            comb.push_back({path, 1});
        } else {
            it->second++;
        }
        return;
    }

    for (int i = start; i < s.size(); ++i) {
        combine(s, i + 1, path + s[i], r, comb);
    }
}

vector<string> solution(vector<string> orders, vector<int> course) {
    vector<string> result;

    // 1. orders 문자열을 사전순 정렬
    for (string& order : orders) {
        sort(order.begin(), order.end());
    }

    // 2. 각 course 크기에 대해 처리
    for (int courseSize : course) {
        vector<pair<string, int>> comb; // 조합 문자열, 주문한 손님 수

        // 모든 주문에서 해당 길이의 조합 생성 및 카운팅
        for (const string& order : orders) {
            combine(order, 0, "", courseSize, comb);
        }

        // 최대 카운트 계산 (2 이상인 것만)
        int maxCount = 0;
        for (const auto& p : comb) {
            if (p.second >= 2 && p.second > maxCount) {
                maxCount = p.second;
            }
        }

        // 최대 카운트 조합만 결과에 추가
        for (const auto& p : comb) {
            if (p.second == maxCount && maxCount >= 2) {
                result.push_back(p.first);
            }
        }
    }

    sort(result.begin(), result.end());
    return result;
}

알게된 것

1. find_if()

C++ STL <algorithm> 헤더에 포함된 함수이다.
"조건을 만족하는 첫 번째 요소를 찾아서 반복자(iterator)를 반환"한다.

기본 문법

auto it = std::find_if(start, end, 조건함수);

start에는 탐색할 데이터의 시작, end에는 끝을 넣어준다.
조건함수에는 각 요소에 대해 true 또는 false를 반환하는 함수를 넣어준다. 이때 보통 간결함을 위해 람다함수를 사용한다!

ex) vector<pair<string, int>>에서 문자열이 특정 값인 것 찾기

vector<pair<string, int>> menu = {{"AC", 1}, {"ACD", 2}, {"ACDE", 3}};

string target = "ACD";

auto it = find_if(menu.begin(), menu.end(), [&](const pair<string, int>& p) {
    return p.first == target;
});

if (it != menu.end()) {
    cout << "찾음: " << it->first << ", count: " << it->second << endl;
}

여기서 마지막 조건함수 부분이 어렵기도 하고 중요하다.

[&](const pair<string, int>& p) {
    return p.first == target;
}

람다함수 구조

[캡처](매개변수) -> 반환타입 { 함수본문 }

1.
맨 앞부분의 [&]는 바깥의 변수들을 자동으로 참조(&)해서 접근하겠다는 일종의 선언이다.
캡처 방식으로 사용할 수 있는 것들은 아래와 같다.

캡처 방식의미바깥 변수 사용 가능?
[]캡처 없음❌ 에러
[&]참조 캡처✅ 가능
[=]값 캡처✅ 가능

그러면 무조건 [&]를 쓰는 게 컴파일 에러도 안 나고 좋은 거 아니냐? 하면 아니다.
[&]는 편리하지만, 값을 참조해서 사용하기 때문에 의도하지 않는 변경이 일어날 수 있다. 그렇기에 필요할 때만 조심해서 사용해야 한다.

위 예시 코드에서는 외부에 있는 target 변수를 참조하고 있기 때문에 [&]를 사용하고 있다. 값 변경은 하지 않지만 [=]를 사용하면 복사가 일어나 메모리 낭비가 발생하기 때문에 [&]를 사용한다.

2.
(const pair<string, int>& p)는 매개변수로, 검사할 배열의 원소 하나의 타입을 넣어준다고 보면 된다.

3.
{ return p.first == target }는 간단하게 true 혹은 false를 반환할 조건을 명시해주면 된다.

2. erase()

STL 컨테이너(vector, string 등)에서 원소를 제거하는 함수이다.
컨테이너에 따라서 사용법이 조금씩 다르지만 대표적으로 vectorstring에서의 사용법을 설명하겠다.

1) vector에서의 erase()

vector<int> v = {10, 20, 30, 40, 50};

// 하나 제거 
v.erase(v.begin() + 1);  // 20 제거 → {10, 30, 40, 50}

// 범위 제거 (30 ~ 40 제거)
v.erase(v.begin() + 1, v.begin() + 3);  // → {10, 50}

2) string에서의 erase()

str.erase(pos);             // pos번째 문자 하나 제거
str.erase(pos, len);        // pos부터 len개 문자 제거(개수로 범위 지정)
str.erase(iterator);        // 반복자로 제거
str.erase(start, end);      // 반복자 범위 제거(주소값으로 범위 지정)

3. auto 타입

파이썬에서처럼, 타입을 자동으로 추론해주는 키워드이다.

auto x = 10;         // int
auto y = 3.14;       // double
auto s = "hello";    // const char*
auto str = string("hi");  // string

반복자 타입이 복잡하거나, 함수 반환 타입이 길 때 사용하면 좋다.

profile
환영합니다!

0개의 댓글