프로그래머스 | Level 2 | 이모티콘 할인행사
Programmers | Level 2 | 이모티콘 할인행사
Problems
- 📘 Src: Programmers
- 🗂️ Cat: Brute Force
Description
카카오톡에서는 이모티콘을 무제한으로 사용할 수 있는 이모티콘 플러스 서비스 가입자 수를 늘리려고 합니다. 이를 위해 카카오톡에서는 이모티콘 할인 행사를 하는데, 목표는 다음과 같습니다.
- 이모티콘 플러스 서비스 가입자를 최대한 늘리는 것.
- 이모티콘 판매액을 최대한 늘리는 것.
1번 목표가 우선이며, 2번 목표가 그 다음입니다.
이모티콘 할인 행사는 다음과 같은 방식으로 진행됩니다.
n
명의 카카오톡 사용자들에게 이모티콘m
개를 할인하여 판매합니다.- 이모티콘마다 할인율은 다를 수 있으며, 할인율은 10%, 20%, 30%, 40% 중 하나로 설정됩니다.
카카오톡 사용자들은 다음과 같은 기준을 따라 이모티콘을 사거나, 이모티콘 플러스 서비스에 가입합니다.
- 각 사용자들은 자신의 기준에 따라 일정 비율 이상 할인하는 이모티콘을 모두 구매합니다.
- 각 사용자들은 자신의 기준에 따라 이모티콘 구매 비용의 합이 일정 가격 이상이 된다면, 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입합니다.
다음은 2명의 카카오톡 사용자와 2개의 이모티콘이 있을때의 예시입니다.
사용자 | 비율 | 가격 |
---|---|---|
1 | 40 | 10,000 |
2 | 25 | 10,000 |
이모티콘 | 가격 |
---|---|
1 | 7,000 |
2 | 9,000 |
1번 사용자는 40%이상 할인하는 이모티콘을 모두 구매하고, 이모티콘 구매 비용이 10,000원 이상이 되면 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입합니다.
2번 사용자는 25%이상 할인하는 이모티콘을 모두 구매하고, 이모티콘 구매 비용이 10,000원 이상이 되면 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입합니다.
1번 이모티콘의 가격은 7,000원, 2번 이모티콘의 가격은 9,000원입니다.
만약, 2개의 이모티콘을 모두 40%씩 할인한다면, 1번 사용자와 2번 사용자 모두 1,2번 이모티콘을 구매하게 되고, 결과는 다음과 같습니다.
사용자 | 구매한 이모티콘 | 이모티콘 구매 비용 | 이모티콘 플러스 서비스 가입 여부 |
---|---|---|---|
1 | 1, 2 | 9,600 | X |
2 | 1, 2 | 9,600 | X |
이모티콘 플러스 서비스 가입자는 0명이 늘어나고 이모티콘 판매액은 19,200원이 늘어납니다.
하지만, 1번 이모티콘을 30% 할인하고 2번 이모티콘을 40% 할인한다면 결과는 다음과 같습니다.
사용자 | 구매한 이모티콘 | 이모티콘 구매 비용 | 이모티콘 플러스 서비스 가입 여부 |
---|---|---|---|
1 | 12 | 5,400 | X |
2 | 1, 2 | 10,300 | O |
2번 사용자는 이모티콘 구매 비용을 10,000원 이상 사용하여 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입하게 됩니다.
따라서, 이모티콘 플러스 서비스 가입자는 1명이 늘어나고 이모티콘 판매액은 5,400원이 늘어나게 됩니다.
카카오톡 사용자 n
명의 구매 기준을 담은 2차원 정수 배열 users
, 이모티콘 m
개의 정가를 담은 1차원 정수 배열 emoticons
가 주어집니다. 이때, 행사 목적을 최대한으로 달성했을 때의 이모티콘 플러스 서비스 가입 수와 이모티콘 매출액을 1차원 정수 배열에 담아 return
하도록 solution
함수를 완성해주세요.
Constraints
- 1 ≤
users
의 길이 =n
≤ 100users
의 원소는[비율, 가격]
의 형태입니다.users[i]
는i+1
번 고객의 구매 기준을 의미합니다.비율
% 이상의 할인이 있는 이모티콘을 모두 구매한다는 의미입니다.- 1 ≤
비율
≤ 40
- 1 ≤
가격
이상의 돈을 이모티콘 구매에 사용한다면, 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입한다는 의미입니다.- 100 ≤
가격
≤ 1,000,000 가격
은 100의 배수입니다.
- 100 ≤
- 1 ≤
emoticons
의 길이 =m
≤ 7emoticons[i]
는i+1
번 이모티콘의 정가를 의미합니다.- 100 ≤
emoticons
의 원소 ≤ 1,000,000 emoticons
의 원소는 100의 배수입니다.
Examples
users | emoticons | result |
---|---|---|
[[40, 10000], [25, 10000]] | [7000, 9000] | [1, 5400] |
[[40, 2900], [23, 10000], [11, 5200], [5, 5900], [40, 3100], [27, 9200], [32, 6900]] | [1300, 1500, 1600, 4900] | [4, 13860] |
Solutions
Code
import java.util.*;
class Solution {
public int[] solution(int[][] users, int[] emoticons) {
// 모든 할인 조합을 생성
List<int[]> combinations = new ArrayList<>(); // 모든 할인 조합을 저장할 리스트
generate(emoticons, new int[emoticons.length], 0, combinations);
int[] result = new int[2]; // 결과를 저장할 배열 [이모티콘 플러스 서비스 가입자 수, 매출액]
// 생성된 할인 조합을 평가
for (int[] discounts : combinations) {
int[] currentResult = new int[2]; // 현재 할인 조합에 대한 결과 [가입자 수, 매출액]
// 각 사용자에 대해 할인 조합을 평가
for (int[] user : users) {
int minDiscountRate = user[0]; // 사용자의 최소 할인율 기준
int subscriptionThreshold = user[1]; // 서비스 가입 기준 금액
int totalAmount = 0; // 이모티콘 구매 금액
// 이모티콘별로 구매 여부 평가
for (int i = 0; i < emoticons.length; i++) {
if (discountRate(emoticons[i], discounts[i]) >= minDiscountRate) {
totalAmount += discounts[i]; // 할인율이 기준을 만족하면 구매
}
}
// 총 구매 금액이 기준 금액을 초과하면 서비스 가입, 그렇지 않으면 구매
if (totalAmount >= subscriptionThreshold) {
currentResult[0]++; // 서비스 가입자 수 증가
} else {
currentResult[1] += totalAmount; // 이모티콘 판매액 증가
}
}
// 가입자 수와 매출액을 기준으로 결과 갱신
if (result[0] < currentResult[0]) {
result = new int[]{currentResult[0], currentResult[1]};
} else if (result[0] == currentResult[0] && result[1] < currentResult[1]) {
result[1] = currentResult[1];
}
}
return result; // 최종 결과 반환
}
// 모든 할인 조합을 재귀적으로 생성하는 함수
private void generate(int[] emoticons, int[] currents, int index, List<int[]> combinations) {
if (index == emoticons.length) {
combinations.add(currents.clone()); // 모든 할인율이 결정되면 조합 저장
return;
}
int[] discounts = new int[]{10, 20, 30, 40}; // 각 할인율에 대해 재귀적으로 조합 생성
for (int discount : discounts) {
currents[index] = emoticons[index] * (100 - discount) / 100; // 할인 적용
generate(emoticons, currents, index + 1, combinations); // 다음 이모티콘으로 이동
}
}
// 할인율을 계산하는 함수
private int discountRate(int retailPrice, int discountPrice) {
return 100 - discountPrice * 100 / retailPrice; // 정가와 할인가를 통해 할인율 계산
}
}
Approaches
이 문제는 완전 탐색(Brute Force) 기법을 사용하여 모든 할인율 조합을 시도한 후, 각 조합에 대해 사용자의 반응을 시뮬레이션하고 최적의 결과를 찾는 방식입니다. 주어진 할인율 조합에 따라 이모티콘을 구매할지 또는 서비스에 가입할지를 결정하는 과정을 반복해서 평가합니다. 이모티콘의 수가 적으므로, 가능한 모든 할인율 조합을 생성하여 탐색하는 방식이 유효합니다.
1. 문제 분석
이모티콘 할인 행사는 각 사용자에게 주어진 할인율을 평가하고, 그 할인율에 따라 이모티콘 구매 또는 서비스 가입 여부를 결정하는 문제입니다. 각 사용자마다 할인율 기준과 가격 기준이 다르며, 이 두 가지 기준을 고려하여 가장 많은 사용자가 이모티콘 플러스 서비스에 가입하도록 할인율을 설정해야 합니다.
문제에서 주어진 할인율은 10%, 20%, 30%, 40%로 고정되어 있으므로, 각 이모티콘에 대해 가능한 모든 할인율 조합을 생성하고, 그 조합에 따른 결과를 평가하는 완전 탐색 방식으로 문제를 해결할 수 있습니다.
2. 접근 방식
이 문제는 모든 할인율 조합을 평가하여 최적의 결과를 찾는 완전 탐색 방식으로 해결할 수 있습니다. 주어진 할인율이 제한적이므로, 이모티콘의 개수가 작다면 모든 가능한 할인율 조합을 생성하고 탐색하는 것이 가능합니다. 기본적인 해결 과정은 다음과 같습니다:
- 할인 조합 생성: 각 이모티콘에 대해 10%, 20%, 30%, 40%의 할인율을 적용한 모든 가능한 조합을 생성합니다. 이는 재귀적으로 생성할 수 있습니다.
- 사용자 반응 평가: 각 할인율 조합에 대해, 모든 사용자가 할인율 기준에 맞춰 이모티콘을 구매할지, 혹은 구매 대신 서비스에 가입할지를 결정합니다. 구매 금액이 사용자 기준 금액을 초과하면 서비스에 가입하고, 그렇지 않으면 이모티콘을 구매합니다.
- 최적 결과 도출: 각 할인율 조합에 대해 서비스 가입자 수와 이모티콘 판매액을 계산하고, 가장 많은 가입자를 유치할 수 있는 할인율 조합을 찾아 최종 결과로 반환합니다.
3. 할인 조합 생성
재귀적으로 모든 이모티콘에 대한 할인율 조합을 생성합니다. 각 이모티콘은 10%, 20%, 30%, 40% 중 하나의 할인율을 가질 수 있으므로, 총 4^m(m은 이모티콘 수)개의 조합이 생성됩니다. 이를 통해 모든 가능성을 탐색하게 됩니다.
public int[] solution(int[][] users, int[] emoticons) {
// 모든 할인 조합을 생성
List<int[]> combinations = new ArrayList<>(); // 모든 할인 조합을 저장할 리스트
generate(emoticons, new int[emoticons.length], 0, combinations);
...
}
private void generate(int[] emoticons, int[] currents, int index, List<int[]> combinations) {
if (index == emoticons.length) {
combinations.add(currents.clone()); // 조합 저장
return;
}
// 이모티콘마다 할인율 설정 (10%, 20%, 30%, 40%)
int[] discounts = new int[]{10, 20, 30, 40};
for (int discount : discounts) {
currents[index] = emoticons[index] * (100 - discount) / 100; // 할인 적용
generate(emoticons, currents, index + 1, combinations); // 재귀적으로 다음 이모티콘 처리
}
}
4. 사용자 반응 평가
생성된 각 할인 조합에 대해, 사용자가 설정한 할인율 기준과 가격 기준을 바탕으로 해당 할인 조합에 대한 반응을 평가합니다. 사용자가 기준을 충족하는지 확인하고, 그에 따라 서비스 가입 또는 이모티콘 구매 여부를 결정합니다.
// 생성된 할인 조합을 평가
for (int[] discounts : combinations) {
int[] currentResult = new int[2]; // 현재 조합에 대한 결과
// 각 사용자에 대해 평가
for (int[] user : users) {
int minDiscountRate = user[0]; // 사용자가 원하는 최소 할인율
int subscriptionThreshold = user[1]; // 서비스 가입 기준 금액
int totalAmount = 0; // 총 구매 금액 계산
// 이모티콘 구매 여부 평가
for (int i = 0; i < emoticons.length; i++) {
if (discountRate(emoticons[i], discounts[i]) >= minDiscountRate) {
totalAmount += discounts[i]; // 할인율을 만족하면 구매
}
}
// 서비스 가입 여부 결정
if (totalAmount >= subscriptionThreshold) {
currentResult[0]++; // 서비스 가입자 수 증가
} else {
currentResult[1] += totalAmount; // 판매액 증가
}
}
...
}
5. 서비스 가입자 수가 가장 많으면서 매출액이 높은 결과 선택
각 할인율 조합에 대해 평가한 후, 최종적으로 이모티콘 플러스 서비스 가입자 수가 가장 많은 조합을 선택해야 합니다. 여기서 가장 중요한 우선순위는 서비스 가입자 수입니다. 만약 여러 조합에서 동일한 가입자 수를 기록했다면, 그중에서 매출액이 더 높은 결과를 선택합니다.
이 과정을 통해 서비스 가입자 수가 최대인 조합을 우선적으로 고려하며, 가입자 수가 같다면 매출액을 기준으로 최적의 결과를 선택하게 됩니다.
int[] result = new int[2]; // 결과를 저장할 배열 [이모티콘 플러스 서비스 가입자 수, 매출액]
for (int[] discounts : combinations) {
...
// 최종적으로 서비스 가입자 수가 가장 많은 결과를 선택
if (result[0] < currentResult[0]) {
result = new int[]{currentResult[0], currentResult[1]}; // 가입자 수가 더 많을 때 갱신
} else if (result[0] == currentResult[0] && result[1] < currentResult[1]) {
result[1] = currentResult[1]; // 가입자 수가 같을 경우, 매출액이 더 높은지 확인하여 갱신
}
}
6. 최적 결과 반환
모든 할인율 조합을 평가한 후, 가장 많은 서비스 가입자와 가장 높은 매출액을 기록한 조합을 최종 결과로 반환합니다. 최적의 할인율 조합을 통해 달성한 서비스 가입자 수와 매출액을 반환합니다.
// 최종적으로 최적의 결과 반환
return result;
Complexity
- ⏳ TC: O(4^n * m)
- 💾 SC: O(4^n)
각 이모티콘에 대해 4가지 할인율을 적용할 수 있으므로, 가능한 할인율 조합은 O(4^n)입니다. 각 할인율 조합에 대해 n명의 사용자에 대한 평가를 수행하므로 시간 복잡도는 O(4^n * m)입니다. 가능한 모든 할인율 조합을 저장해야 하므로, 공간 복잡도는 O(4^n)입니다.
- Algorithm
- Brute Force