본문 바로가기

알고리즘풀이/프로그래머스

[2/5] Kakao Internship 기출문제 풀이

👉 사용 언어 : PYTHON3

주차 요금 계산

✅ Solution

from collections import defaultdict
import math

def solution(fees, records):
    answer = []
    times = defaultdict(list)
    for i in records:
        times[i.split(' ')[1]].append((i.split(' ')[0]))
    for j in sorted(times.keys()):
        time = 0
        while len(times[j]) >= 2:
            before_h, before_m = list(map(int, times[j][0].split(':')))
            after_h, after_m = list(map(int, times[j][1].split(':')))
            before_h *= 60
            after_h *= 60
            time += ((after_h + after_m) - (before_h + before_m))
            del times[j][0:2]

        if len(times[j]) != 0:
            time += ((23*60 + 59) - ((int(times[j][0].split(':')[0])*60) + int(times[j][0].split(':')[1])))

        if time <= fees[0]:
            answer.append(fees[1])
        else:
            answer.append(fees[1] + math.ceil((time-fees[0])/fees[2]) * fees[3])

    return answer

 

👉 문제 설명 및 제한 사항

  • 요금 계산식 : 기본 요금 + (누적 주차 시간 - 기본 시간) / 단위 시간 x 단위 요금
    • 즉, fees[1] + ⌈누적 주차 시간 - fees[0]/ fees[2] * fees[3]
    • ⌈a⌉ : a보다 작지 않은 최소의 정수 _ 올림
    • 누적 주차 시간 <= 기본 시간 : 기본 요금 5000원만 청구
  • fees 배열
    • [0] : 1 <= 기본 시간(분) <= 1439
    • [1] : 0 <= 기본 요금(원) <= 100,000
    • [2] : 1 <= 단위 시간(분) <= 1439
    • [3] : 1 <= 단위 요금(원) <= 1,000
  • records 배열 원소
    • "시각 차량번호 내역"
    • 시각 : HH:MM 형식 _ 00:00 ~ 23:59
    • 차량번호 : '0'~'9'로 이루어진 4개의 문자(=문자열)
    • 내역 : IN or OUT
  • 어떤 차량이 입차된 후에 출차된 내역이 없다면, 23:59에 출차된 것으로 간주한다.
  • 리턴할 아웃풋은 차량번호 작은 순으로 출력한다.
  • 같은 차량 번호가 여러번 입/출차하면 누적 주차 시간(합)으로 요금을 계산한다.

👉 아이디어

  • defaultdict를 사용하여 리스트를 value로 가지는 딕셔너리를 생성한다.(초깃값 지정)
  • 딕셔너리의 Key차량번호, Value시각 리스트를 맵핑하여 저장한다.
    • IN-OUT 반복 이므로 따로 IN인지 OUT인지 정보를 가지고 있을 필요가 없다.
  • 리턴할 아웃풋 배열이 차량번호가 작은 순 으로 저장된 주차 요금이므로 반복문을 사용하여 딕셔너리 요소를 꺼낼 때, sorted()를 통해 차량번호(Key)가 작은 것부터 꺼내도록 작성한다.
  • 시각 리스트의 요소 개수가 2개 이상 일 동안 반복문 을 돌린다.
    • 2개씩 잘라서 IN/OUT 시각을 비교 하여 주차 시간을 계산하고 누적 주차 시간에 추가한다.
    • while문이 무한루프 도는 것을 방지 하기 위해 시각 리스트에서 해당 요소 2개 삭제 한다.
    • 반복문이 끝난는데도 요소가 남아있다면 (= 원래 시각리스트 요소 개수가 홀수개였다면) 남은 하나는 OUT 시각23:59로 하여 누적 주차 시간에 추가한다.
  • 차량 번호당 청구해야하는 총 주차 요금을 계산하여 정답 배열에 추가한다.
    • 누적 주차 시간  < 기본 시간 : 기본 요금(fees[1])
    • 누적 주차 시간  >= 기본 시간 : fees[1] + ⌈누적 주차 시간 - fees[0]/ fees[2] * fees[3] 적용

 

 다른 사람 풀이 참고

from collections import defaultdict
from math import ceil

class Parking:
    def __init__(self, fees):
        self.fees = fees
        self.in_flag = False
        self.in_time = 0
        self.total = 0

    def update(self, t, inout):
        self.in_flag = True if inout=='IN' else False
        if self.in_flag:  self.in_time = str2int(t)
        else:             self.total  += (str2int(t)-self.in_time)

    def calc_fee(self):
        if self.in_flag: self.update('23:59', 'out')
        add_t = self.total - self.fees[0]
        return self.fees[1] + ceil(add_t/self.fees[2]) * self.fees[3] if add_t >= 0 else self.fees[1]

def str2int(string):
    return int(string[:2])*60 + int(string[3:])

def solution(fees, records):
    recordsDict = defaultdict(lambda:Parking(fees))
    for rcd in records:
        t, car, inout = rcd.split()
        recordsDict[car].update(t, inout)
    return [v.calc_fee() for k, v in sorted(recordsDict.items())]

 

👉 깨달은 것 / 알게된 것

  • Parking이라는 클래스를 선언하고 그 안에서 변수 4개 및 메서드 2개 선언
    • fees : fees 배열 _ 매개변수로 전달 받음
    • in_flag : IN/OUT인지?
    • in_time : 입차 시각
    • total : 누적 주차 시간
    • update(self, t, inout) : 누적 주차 시간 계산
      • IN : in_time 변수 업데이트 (분 단위)
      • OUT : in_total 변수 업데이트
    • calc_fee(self) : 주차 요금 계산
      • IN : 기본적으로 23:59 출차 고려
  • str2int(string) : (문자열) HH:MM > (정수) 분 단위로 변환
    • 문자열 슬라이싱을 사용하여 코드를 가독성있게 작성하였다.
    • HH는 [:2]로 잘라 *60 해주어서 분으로 변환, MM는 [3:]로 잘라주었다.
  • 딕셔너리 value에 Parking 클래스 인스턴스를 사용할 수 있다.
  • 마찬가지로 차량번호 작은 순이기 때문에 반복문 내에서 딕셔너리의 Key를 기준으로 오름차순 정렬하여 요소를 뽑아내었다.

메뉴 리뉴얼

✅ Solution

from itertools import combinations
from collections import deque

def solution(orders, course):
    answer = deque([])
    result = deque([])
    for i in range(len(orders)):
        for j in range(i+1, len(orders)):
            inters = set(orders[i]).intersection(set(orders[j]))
            for k in course:
                if len(inters) >= k:
                    [answer.append(''.join(sorted(c))) for c in list(combinations(''.join(list(inters)), k))]
    orders_dict = dict.fromkeys(list(set(answer)))
    for key in orders_dict.keys():
        orders_dict[key] = answer.count(key)
    for k in course:
        _max = -1
        for key in orders_dict.keys():
            if len(key) == k and _max <= orders_dict[key]:
                _max = max(_max, orders_dict[key])
        for key in orders_dict.keys():
            if len(key) == k and orders_dict[key] == _max:
                result.append(key)
    return sorted(result)

 

👉 문제 설명 및 제한 사항

  • orders : 각 손님들이 주문한 단품메뉴들이 문자열 형식으로 담긴 배열
    • 길이 : 2 이상 20 이하
    • 각 원소 크기 : 2 이상 10 이하
    • 알파벳 대문자로만 & 알파벳 중복 X
  • course : 추가하고 싶어하는 코스요리를 구성하는 단품메뉴들의 갯수가 담긴 배열
    • 길이 : 1 이상 10 이하
    • 각 원소 크기 : 2 이상 10 이하
    • 오름차순으로 정렬 & 알파벳 중복 X
  • 새로 추가하게 될 코스요리의 메뉴 구성을 문자열 형태로 배열에 담아 return
    • 오름차순으로 정렬 & 배열 길이 1 이상
    • 각 손님들이 주문할 때 가장 많이 함께 주문한 단품메뉴들을 코스요리 메뉴로 구성 

👉 아이디어

  • 중첩 반복문을 통해 두 개의 원소씩 묶어서 교집합을 구한다.
  • 내부에 course 배열에서 반복문을 또 다시 돌려 배열 원소와 교집합의 길이를 비교한다.
    • course 배열 원소(k) <= 교집합 길이일 때, Combinations를 사용하여 k개의 문자를 뽑아 리스트를 만들고 해당 리스트의 원소들을 모두 오름차순 정렬한 뒤 문자열로 변환(''.join())하여 새로운 배열(answer)에 추가한다.
  • answer 배열에서 중복된 원소제거한 뒤, 배열 원소를 key 로 하는 딕셔너리를 생성한다.
    • 딕셔너리의 value로 answer 배열에서의 각 원소(문자열)의 개수를 넣어준다.
  • course 배열에서 반복문을 돌려
    • 각 원소(= 문자열 길이)에 해당하는 딕셔너리 key의 value 중 max 값을 찾는다.
    • 딕셔너리에서 max값을 value로 갖는 모든 key를 정답 배열에 추가한다.
  • 정답 배열을 오름차순 정렬하여 반환한다.

 

 다른 사람 풀이 참고

import collections
import itertools

def solution(orders, course):
    result = []

    for course_size in course:
        order_combinations = []
        for order in orders:
            order_combinations += itertools.combinations(sorted(order), course_size)

        most_ordered = collections.Counter(order_combinations).most_common()
        result += [ k for k, v in most_ordered if v > 1 and v == most_ordered[0][1] ]

    return [ ''.join(v) for v in sorted(result) ]
from itertools import combinations
from collections import Counter

def solution(orders, course):
    answer = []
    
    for c in course:
        candidate = []  # 가능한 모든 조합을 저장할 리스트
        for order in orders:
            candidate += combinations(sorted(order), c)
        count = Counter(candidate)  # 각 조합이 몇 번 주문되었는지 세기 (course로 나올 수 있는 모든 조합)
       
        if count:
            max_count = max(count.values())  # 가장 많이 주문된 조합의 주문 횟수
            print(max_count)
            if max_count >= 2:  # 최소 2번 이상 주문된 조합
                answer += [''.join(key) for key in count if count[key] == max_count]
                
    
    return sorted(answer)

 

👉 깨달은 것 / 알게된 것

  • Collections.Counter()
    • Counter 생성자 : 여러 형태의 데이터를 인자로 받으며 중복된 데이터가 저장된 배열을 인자로 넘기면 각 원소몇 번씩 나오는지가 저장된 객체(= 딕셔너리)를 반환한다.
    • 내가 풀이한 코드에서 배열을 사용하여 딕셔너리를 만들고 반복문을 돌려 카운트 값을 딕셔너리의 value에 저장한 과정을 collections 모듈의 Counter 클래스 하나로 구현해낼 수 있다.
    • 파이썬의 기본 자료구조인 사전(dictionary)를 확장하고 있기 때문에, 사전 에서 제공하는 API를 그대로 다 사용 가능 하다.
    • 데이터의 개수많은 순으로 정렬된 배열을 리턴하는 most_common()이라는 메서드를 제공한다.
  • 참고 : Counter는 마치 숫자처럼 산술 연산자를 사용할 수 있다❗️
counter1 = Counter(["A", "A", "B"])
counter2 = Counter(["A", "B", "B"])
counter1 + counter2

# Counter({'A': 3, 'B': 3})

 

'알고리즘풀이 > 프로그래머스' 카테고리의 다른 글

하노이의 탑  (2) 2024.02.11
[2/10] 기출문제 풀이  (0) 2024.02.08
[2/2] Kakao Internship 기출문제 풀이  (0) 2024.01.31
옹알이 (1)  (2) 2024.01.29
[1/28] Kakao Internship 기출문제 풀이  (0) 2024.01.26