레스토랑을 운영하던 스카피는 코로나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, C | 1번, 2번, 4번, 6번 손님으로부터 총 4번 주문됐습니다. |
| 요리 3개 코스 | C, D, E | 3번, 4번, 6번 손님으로부터 총 3번 주문됐습니다. |
| 요리 4개 코스 | B, C, F, G | 1번, 5번 손님으로부터 총 2번 주문됐습니다. |
| 요리 4개 코스 | A, C, D, E | 4번, 6번 손님으로부터 총 2번 주문됐습니다. |
[문제]
각 손님들이 주문한 단품메뉴들이 문자열 형식으로 담긴 배열 orders, "스카피"가 추가하고 싶어하는 코스요리를 구성하는 단품메뉴들의 갯수가 담긴 배열 course가 매개변수로 주어질 때, "스카피"가 새로 추가하게 될 코스요리의 메뉴 구성을 문자열 형태로 배열에 담아 return 하도록 solution 함수를 완성해 주세요.
[제한사항]
[입출력 예]
| orders | course | result |
|---|---|---|
| ["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"] |
입출력 예에 대한 설명
입출력 예 #1
문제의 예시와 같습니다.
입출력 예 #2
AD가 세 번, CD가 세 번, ACD가 두 번, ADE가 두 번, XYZ 가 두 번 주문됐습니다.
요리 5개를 주문한 손님이 1명 있지만, 최소 2명 이상의 손님에게서 주문된 구성만 코스요리 후보에 들어가므로, 요리 5개로 구성된 코스요리는 새로 추가하지 않습니다.
입출력 예 #3
WX가 두 번, XY가 두 번 주문됐습니다.
3명의 손님 모두 단품메뉴를 3개씩 주문했지만, 최소 2명 이상의 손님에게서 주문된 구성만 코스요리 후보에 들어가므로, 요리 3개로 구성된 코스요리는 새로 추가하지 않습니다.
또, 단품메뉴를 4개 이상 주문한 손님은 없으므로, 요리 4개로 구성된 코스요리 또한 새로 추가하지 않습니다.
import java.util.*;
class Solution {
// 메뉴를 저장할 Map
Map<String, Integer> map;
// 최댓값
int max = 0;
// dfs 탐색 메서드
public void dfs(String order, String key, int index, int end, int depth) {
// 코스의 길이와 동일할 때까지 탐색했을 경우
if(depth == end) {
// map에 key와 value를 넣어줌
map.put(key, map.getOrDefault(key, 0) + 1);
// max값 변경
max = Math.max(max, map.get(key));
}
// dfs 메서드 재귀호출
for(int i = index + 1; i < order.length(); i++) {
dfs(order, key + order.charAt(i), i, end, depth + 1);
}
}
public String[] solution(String[] orders, int[] course) {
ArrayList<String> ans = new ArrayList<>();
// course 배열만큼 반복
for(int c : course) {
// HashMap, max 값 초기화
map = new HashMap<>();
max = 0;
// 주문서만큼 반복
for(String order: orders) {
// 각 주문마다 알파벳 순서대로 정렬
char[] strs = order.toCharArray();
Arrays.sort(strs);
order = new String(strs);
// dfs 탐색 시작
dfs(order, "", -1, c, 0);
}
// map에 저장된 key 개수만큼 반복
for(String key : map.keySet()) {
// key값으로 value를 불러옴
int value = map.get(key);
// value가 2 이상이면서 max랑 동일하다면
if(value > 1 && max == value) {
// 배열에 키값을 저장
ans.add(key);
}
}
}
// 정렬을 진행
Collections.sort(ans);
// String[] 배열로 변환
String[] answer = ans.toArray(new String[ans.size()]);
return answer;
}
}
DFS 탐색을 사용해서 해결하는 문제였다.
course 배열에 있는 값만큼의 음식들의 조합을 찾고, 그 조합의 최댓값을 찾아서 메뉴 구성을 도와주어야한다.
따라서 맨 처음 course 배열의 길이만큼 반복을 진행한다. 반복을 하면서 음식의 조합을 저장할 HashMap과 가장 많이 선택된 조합을 저장할 max 변수를 초기화시켜준다. 그 뒤에 주문한 개수만큼 반복문을 진행하는데 이때, 코스요리 메뉴의 구성을 사전 순으로 정렬해서 return을 해주어야하기 때문에 주문서의 메뉴들을 먼저 사전 순으로 정렬을 진행하였다.
이후 dfs 탐색을 진행한다.
dfs 탐색 메서드에서는 (주문서와 조합, index, 구성할 갯수, 깊이)를 매개변수로 둔다.
구성할 갯수와 깊이가 같아진다면 원하는 조건까지 왔기 때문에 더이상의 탐색을 중단하고 map에 값을 넣어준다. 이때 max값의 비교를 통해 map에 있는 값들 중 가장 큰 값을 찾아낸다.
깊이가 같지 않다면 for문의 반복을 통해 dfs 탐색 메서드를 재귀호출한다. 이런 반복을 통해 한 주문서에 있는 메뉴들의 조합을 모두 map에 저장할 수 있다.
모든 탐색이 끝나면 map에 저장된 key의 갯수만큼 반복을 진행한다. key 값으로 value값을 불러와서 value가 2 이상이면서 max 값과 동일하다면 배열에 해당 조합을 저장해준다.
위의 과정을 course 배열의 길이만큼 반복하고 모든 반복이 끝난 뒤에 ans 배열에는 조건에 만족하는 조합들이 저장되어있을 것이다.
정렬을 진행한 뒤에 String[] 배열로 바꿔주고 반환해주면 문제를 해결할 수 있다!
의도한 건 아니지만 최근 dfs 탐색과 hash를 사용해서 이런 식으로 조합을 저장하는 문제들을 계속 풀고 있다. 원래 이 문제를 봤다면 바로 생각이 나지 않았을 것 같았으나, 최근 이런 문제들을 계속 풀어서 그런지 바로 방식이 생각나서 도전할 수 있었다. 아직 익숙하지 않아 한번에 빠르게 쉽게 풀진 못했다.. 하지만 바로 방식이 생각났다는 것에 굉장히 놀랐고, 이래서 같은 방식의 문제라도 여러 번 반복해서 푸는 연습이 필요하구나를 알 수 있었다!