티스토리 뷰

https://school.programmers.co.kr/learn/courses/30/lessons/150368

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

간만에 이런 식으로 문제를 풀다보니 생각보다 재밌네요.

이번에는 다른 문제를 가져왔습니다. 

 

마찬가지로 카카오에서 나온 문제이고요 

저번 Lv.1 에 비하면 확실히 생각할 부분이 많습니다.

 

천천히 같이 풀어봅시다. 

문제 상황

카카오에서 이모티콘을 무제한으로 사용할 수 있는 서비스의 가입자를 늘리려고 합니다. 

이번 이벤트의 최종 목표는 다음과 동일합니다. 

 

1. 이모티콘 플러스 서비스 가입자를 최대로 늘리는 것 

2. 이모티콘 판매액을 최대로 하는 것 

1번 목표가 최우선이고 2번은 그 다음 입니다. 

 

n명의 사용자에게 이모티콘 m개 할인행사를 진행합니다.

각 이모티콘의 할인률은 10%,20%,30%,40% 중 하나로 진행됩니다.

  

각 사용자들은 자신의 기준보다 많이 할인하는 이모티콘은 무조건 삽니다.

단 이때 총 구매금액이 자신의 기준 금액이상이면 모든 구매를 취소하고 무제한 서비스에 가입합니다.

 

이때 사용자들의 구매 기준(users)과 이모티콘들의 정가(emoticons)가 주어질때 최대 가입자수와 이모티콘 매출액

1차원 정수 배열 ([Int]) 로 출력하세요.

정답 예시

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]

* 추가 설명

- 첫번째 정답을 좀 더 설명하면 각 이모티콘이 할인률이 30%(7000원),40%(9000원) 할인할때 최대 효율이라고 하면 

사용자 구매 이모티콘 이모티콘 구매비용 (할인 적용) 서비스 가입 여부
[40,10000] 9000원 5400원 X
[25,10000] 7000원,9000원 10300원 O

- 따라서 가입자 1명에 이모티콘 판매수익은 5400원이 됩니다.

문제 풀이 (swift)

저번과 마찬가지로 단계를 나누어 풀어보겠습니다.

 

1. 이모티콘 할인률 적용 경우의 수 구하기

2. 할인률 별 사용자들의 이모티콘 구매 금액과 서비스 가입자 수 구하기

3. 가장 많은 서비스 가입을 하는 경우와 금액 구하기   

1번이 제가 보기엔 제일 메인 미션인 것 같아요.

 

제 생각에 이 문제의 포인트는

각 이모티콘을 얼마나 할인해야 최대 효율인지 스스로 알아내야한다는 점 입니다. 

 

1. 이모티콘 할인률 적용 경우의 수 구하기

먼저 m개의 이모티콘을 각각 얼마나 할인할지를 나타내는 배열을 만들꺼에요

근데 이걸 모든 경우의 수를 구해야 가장 좋은 경우의 수를 구할 수 있겠죠?

 

* 추가 설명 

- 사실 이 문제는 말이 어려울 뿐 경우의 수에서는 자주 보이는 문제와 동일합니다. 

- [10%,20%,30%,40%] 를  순서대로 m개 나열하시오 랑 같은 말입니다.(중복 사용 가능)

- 다른 말로는 중복 순열이라 합니다.

- 이 부분의 원리는 시간이 되면 따로 올려보겠습니다.

이 원리라고 생각하면 됩니다.

좀 더 쉽게 이야기 해보면 같은 구조의 반복이라 할 수 있습니다.

반복 -> 재귀로 표현하면 좀 더 쉽겠죠?

Tip. m이 1개이거나 2개일때 정도만 생각해서 만드시면 좀 더 쉽습니다.  

func getSalesArray(_ length : Int,_ count : Int) -> [[Int]] {
    let caseSample = [10,20,30,40]
    if(count == 1){
        return [[10],[20],[30],[40]] // 한개인 경우는 별도 계산 X
    }else{ // 이전 배열에 각각 하나씩 추가한 형태로 만들기 
        let lastArray = getSalesArray(length, count-1)
        var newArrays : [[Int]] = []
        for i in lastArray {
            for j in caseSample{
                var newElement = i
                newElement.append(j)
                newArrays.append(newElement)
            }
        }
        return newArrays
    }
}

실제로 저 함수를 돌려보면 아래처럼 나오게 됩니다.

print(getSalesArray(3, 3)) // 3개짜리로 만드시오

이런 결과 값이 나오게 됩니다.

2. 할인률 별 사용자들의 이모티콘 구매 금액과 서비스 가입자 수 구하기

여기부터는 이제 좀 쉽습니다. 

users, emoticons, 그리고 위에서 구한 경우의 수 중 한가지(sales)를 넣었을때 

총 가입자 수와 금액을 구해주는 함수를 만들면 됩니다.

그럼 각각 유저 한명의 가입여부 + 구매 금액부터 알아야겠죠? 

 

유저 한명의 가입 여부+ 구매 금액 (ex. 앞 자리가 1이면 가입 / 0이면 미가입)

// 개인 사용자 가입 여부 및 총 금액
func getPersonValue(_ user : [Int] ,_ emoticons:[Int],_ sales:[Int]) -> [Int] {
    var total = 0
    for i in 0..<sales.count{
        if(sales[i] >= user.first!){
            total += emoticons[i] * (100 - sales[i]) / 100
            if(total >= user.last!){
                // 기준 금액이 넘어가는 순간 더이상의 계산은 불필요하다.
                return [1,0]
            }
        }
    }
    if(total >= user.last!){
        return [1,0]
    }
    return [0,total]
}

이제 개인 가입 여부와 금액을 알아냈으니 합산해볼까요?

합산은 간단히 for 문을 이용해 줍시다.

// 각 케이스별 가입자수 및 전체 금액
func getCaseResult(_ users:[[Int]],_ emoticons:[Int],_ sales:[Int]) -> [Int] {
    // sales : 할인 퍼센트
    var totalSales = 0 // 전체 판매 금액
    var totalSign = 0 // 전체 가입자 수

    for i in users{
        let userValue = getPersonValue(i, emoticons, sales)
        if userValue.first! > 0 {
            totalSign += 1
        }else{ // 가입 안함 -> 금액 합산
            totalSales += userValue.last!
        }
    }
    return [totalSign,totalSales] // [가입자수,판매수익]
}

3. 가장 많은 서비스 가입을 하는 경우와 금액 구하기 

자 이제 끝이 눈 앞에있습니다. 비교문만 돌리면 되요. 

func solution(_ users:[[Int]], _ emoticons:[Int]) -> [Int] {
    let salesPercents = getSalesArray(emoticons.count, emoticons.count)
    var resultCase = [0,0]
    for i in salesPercents {
        let now = getCaseResult(users, emoticons, i) // [총 가입자수,판매수익]
        if(now.first! == users.count){//사용자 전원 가입
            return now // 최적의 상황이기에 더이상 할필요 X
        }
        if(now.first! > resultCase.first!){ // 가입자수가 저장된 경우보다 많은 경우
            resultCase = now
        }else if(now.first! == resultCase.first!){ // 가입자수 동일
            if(now.last! > resultCase.last!){ // 판매 수익 비교
                resultCase = now
            }
        }
    }
    return resultCase
}

사실상 경우의 수를 구하는 부분을 제외하면 

많이 어려운 부분은 없는 문제입니다. 

 

천천히 풀면 생각보다 쉽게 풀 수 있습니다.

이제 정리해봅시다.

최종 코드

import Foundation

func solution(_ users:[[Int]], _ emoticons:[Int]) -> [Int] {
    let salesPercents = getSalesArray(emoticons.count, emoticons.count)
    var resultCase = [0,0]
    for i in salesPercents {
        let now = getCaseResult(users, emoticons, i)
        if(now.first! == users.count){
            return now // 최적의 상황이기에 더이상 할필요 X
        }
        if(now.first! > resultCase.first!){
            resultCase = now
        }else if(now.first! == resultCase.first!){
            if(now.last! > resultCase.last!){
                resultCase = now
            }
        }
    }
    return resultCase
}
// 전체 할인 경우의 수 구하기
func getSalesArray(_ length : Int,_ count : Int) -> [[Int]] {
    let caseSample = [10,20,30,40]
    if(count == 1){
        return [[10],[20],[30],[40]]
    }else{
        let lastArray = getSalesArray(length, count-1)
        var newArrays : [[Int]] = []
        for i in lastArray {
            for j in caseSample{
                var newElement = i
                newElement.append(j)
                newArrays.append(newElement)
            }
        }
        return newArrays
    }
}

// 각 케이스별 가입자수 및 전체 금액
func getCaseResult(_ users:[[Int]],_ emoticons:[Int],_ sales:[Int]) -> [Int] {
    // sales : 할인 퍼센트
    var totalSales = 0 // 전체 판매 금액
    var totalSign = 0 // 전체 가입자 수

    for i in users{
        let userValue = getPersonValue(i, emoticons, sales)
        if userValue.first! > 0 {
            totalSign += 1
        }else{ // 가입 안함
            totalSales += userValue.last!
        }
    }
    return [totalSign,totalSales]
}
// 개인 사용자 가입 여부 및 총 금액
func getPersonValue(_ user : [Int] ,_ emoticons:[Int],_ sales:[Int]) -> [Int] {
    var total = 0
    for i in 0..<sales.count{
        if(sales[i] >= user.first!){
            total += emoticons[i] * (100 - sales[i]) / 100
            if(total >= user.last!){
                // 넘어가는 순간 더이상의 계산은 불필요하다.
                return [1,0]
            }
        }
    }
    if(total >= user.last!){
        return [1,0]
    }
    return [0,total]
}

 

실제로 아래 함수를 돌려보면 문제에서 요구한 답변과 동일한 값을 얻을 수 있습니다.

print(solution([[40, 10000], [25, 10000]], [7000, 9000]))
print(solution([[40, 2900], [23, 10000], [11, 5200], [5, 5900], [40, 3100], [27, 9200], [32, 6900]], [1300, 1500, 1600, 4900]))

결과

처음에는 이게 무슨 문제인지 감이 안왔는데 

천천히 해보니까 생각보다 쉽게 풀리네요.

연습하시는 분들에게도 좋은 문제인 것 같아요. 

오늘도 파이팅입니다.

댓글