https://leetcode.com/problems/maximum-performance-of-a-team/

 

Maximum Performance of a Team - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

문제를 풀며 적은 사고 과정 정리.

 

배운 점) DP가 왜 안되는지, Greedy 정당성 증명, 내가 생각한 아이디어 구현하는 구현력 기르기 

 


'At most' k
-> DP로 모든 경우 탐색 각인데

 


1. BF
C(N, K) -> 말도 안되게 큰 숫자. 무조건 안됨

 

 



2. DP
choose(n, k) : idx = n 부터 k명 뽑을 때 max performance
choose(n, k) = max(choose(n+1, k-1) + new_performance, choose(n+1, k)) // 뽑 or 안뽑
어? 근데 performance 구하려면, speed 합 * min_efficiency 필요함.
min_efficiency는 '이전 정보'에 해당
게다가 O(NK) memo 하려면 10^10 메모리 필요 (10GB)

문제점 1) 메모리 → N < K인 경우는 제외, 10^10 / 2
그래도 많은데..

문제점 2) min_eff 정보?
안그래도 메모리 부족한데 배열 하나 더 써??
→ efficiency decreasing order로 정렬해서 관리하면 매번 새로운 사람 뽑을때마다 min_eff 갱신돼서 트래킹 안해도 됨 (Hint 보고 깨달음)

문제점 3) modulo 때문에 max 비교 이상하게 되지 않을까?

정의를 바꿔보자.
choose(n) : n을 뽑았을 때 max perf
k 정보? 역시나 min eff 정보도 여전히 모름. 문제점이 너무 많네.

 

→ 슬슬 DP로는 못풀겠다는 생각이 들었음.

근데 Greedy를 생각해봐도, 뭘 greedy 하게 선택해야하는지 감이 안옴

 

 


3. Greedy
Performance = sum(speed) * min(eff)

Try 1)
speed는 덧셈인데
efficiency는 곱셈이라.. efficiency 최대한 높이는게 맞을까? eff 내림차순 정렬해서 top K 뽑기?

반례) N = 4, K = 2
1 2 1000 2000
100 99 2 3

1 2 뽑 -> 3 * 99 
1000 2000 뽑 -> 3000 * 2

느낌상 speed나 eff 순으로 뽑는 그리디는 아닌 것 같은데... 흠

Try 2)
speed * efficiency 순으로 뽑아?
반례) N = 3, K = 2
[10000, 1, 9]
[1, 1001, 1002]
10000 보다는 1, 9를 고르는게 이득임

K번 루프 반복 O(N*K)는 말도 안되고,
efficiency 순으로 정렬한 뒤, 하나 선택하면 그 이전 선택지는 버려도 되나?
-> TC 몇 개 가지고 놀아본 결과, 하나를 시작점으로 선택했을 때, 그 이전 케이스 선택할 때도 있음

 


→ 여기까지 1~2시간 정도 고민하고 결국 해답 봄

https://leetcode.com/problems/maximum-performance-of-a-team/discuss/741822/Met-this-problem-in-my-interview!!!-(Python3-greedy-with-heap)

 

1. 일단 K조건을 없애서 간단한 문제를 생각해보자.
efficiency 순으로 내림차순 정렬.
답이 되는 Best team이 있다고 가정하고, 그 팀의 min eff = e_m
그럼, e >= e_m 인 선수들을 모조리 다 편입시키는게 이득이지. speed가 늘어날 수록 e_m이랑 곱해져서 perfomance 커지니까.


2. 이제 K명만 선택한다는 조건을 넣어보자.
반대로 생각하면, 기존 e >= e_m 팀에서 누군가 방출해야 하는 상황.
누구를 방출해야 performance가 최대한 유지될까? 

Case 1) e > e_m 인 사람
> 그 중 speed가 가장 작은 사람 방출

Case 2) 가장 마지막 사람 e_m
> min efficiency가 두번째로 작은 사람이 됨

근데 잘 생각해보면, Case 2는 Case 1에 포함됨을 알 수 있다.
e_m이 speed가 가장 작으면 방출해버리는게 낫다. 
어차피 남은 사람들의 efficiency가 더 크기 때문에.

즉, 우리는 처음에 K 조건 생각하지 않고 Best Team 찾은 뒤,
만약 size > K 라면 speed 가장 작은 사람 순으로 방출하면 된다.
(size <= K 일땐 'at most K' 조건 덕에 그냥 답이 됨)


Q. Best Team에서 몇 명 방출해서 K명 만드는 것 vs 애초에 처음부터 best는 아니지만 K명인 team 구하는 것
둘 중 전자 performance가 더 크다는 걸 증명할 수 있나?
> ㅇㅇ. 후자를 size K인 Team L 이라고 해보자.
Best Team 에서 몇 명 제거해서 size K를 만들어야 하는데, 제거에 두 가지 경우가 있다
1) Team L에 속한 사람을 제거 -> Team L이 optimal 하지 않다는 소리. 제거 결과 Team L보다 뛰어난 performance의 팀이 만들어진다.
2) Team L 바깥에 있는 사람만 제거 -> 제거 결과 결국 Team L이 된다.

 

 

그래서 아이디어 따라 best team 찾고 그 중 K명이 될 때까지 Spd 낮은 사람 방출하는 코드를 짜봄. 결과는 WA

 

# Sol 1) (WA) min e_m 을 제거하게 더 나을수도 (반례)
import heapq
def maxPerformance(self, n: int, speed: List[int], efficiency: List[int], k: int) -> int:
zipped_lists = zip(speed, efficiency)
sorted_pairs = sorted(zipped_lists, key = lambda tup : tup[1], reverse = True)
tuples = zip(*sorted_pairs) # unzip
speed, efficiency = [ list(tuple) for tuple in tuples]
# Get best team (without K constraint)
perf = 0
spd_sum = 0
min_idx = -1
for idx, s in enumerate(speed):
eff = efficiency[idx]
spd_sum += s
if spd_sum * eff > perf:
min_idx = idx
perf = spd_sum * eff
# init pq and pop people (with lower spd) to meet K
pq = []
for idx in range(min_idx+1):
heapq.heappush(pq, (speed[idx], efficiency[idx]))
while len(pq) > k:
heapq.heappop(pq)
# Get result
pq.sort(key = lambda t : -t[1])
spd_sum = 0
for t in pq:
spd_sum += t[0]
min_eff = pq[-1][1]
return spd_sum * min_eff

 

반례)
5
[6,6,10,5,8]
[8,2,8,10,6]
3

Before pop [(5, 10), (6, 8), (10, 8), (8, 6)]
(5, 10) 을 없앨게 아니라 (8, 6)을 없애야 답임

Best team 구해두고 spd가 가장 작은 직원 없애는게 아닌가??


잘 생각해보니 위에서 Case 2가 Case 1에 포함될 때는, min_idx 사람의 spd가 가장 낮을 때만임.

Case 1과 Case 2를 모두 해보고, performance 잘 나오는 쪽을 선택하도록 구현하면?

-> 그래도 WA 뜨더라.

# Sol 2) (WA) Sol 1의 pop 놓친 부분 고침 - 그래도 WA
import heapq
class Solution:
def calculate_perf(self, team):
team.sort(key = lambda t : -t[1]) # min_eff 찾기 위해 무조건 sorting -> 비효율적
spd_sum = 0
for spd, eff in team:
spd_sum += spd
return spd_sum * team[-1][1]
def maxPerformance(self, n: int, speed: List[int], efficiency: List[int], k: int) -> int:
# Get best team (without K constraint)
people = sorted(zip(speed, efficiency), key = lambda tup : -tup[1]) # (spd, eff) in efficiency decreasing order
perf = 0
spd_sum = 0
min_idx = -1
for idx, (s, eff) in enumerate(people):
spd_sum += s
if spd_sum * eff > perf:
min_idx = idx
perf = spd_sum * eff
# init pq
pq = []
for idx, (s, eff) in enumerate(people):
if idx > min_idx:
break
heapq.heappush(pq, (speed[idx], efficiency[idx]))
# Pop people to meet K (2 cases)
while len(pq) > k:
cpy = pq[:]
# 1. Spd 가장 낮은 사람 pop
heapq.heappop(pq)
perf1 = self.calculate_perf(pq[:])
# 2. min_idx pop
cpy.sort(key = lambda t : -t[1])
cpy.pop()
perf2 = self.calculate_perf(cpy)
if perf1 < perf2:
pq = cpy
MODULO = int(1e9 + 7) # int 안씌우면 double 처리돼서 % 연산 시 오차 발생
return self.calculate_perf(pq) % MODULO

 

그냥 해설 코드 따라서 처음부터 PQ 쓰고, K명 유지한 채로 max performance 업데이트 하는 식으로 구현해서 통과함. 

# Sol 3) 처음부터 PQ 이용, spd 낮은 사람 방출해가며 K명 유지 + max performance 트래킹
class Solution:
def maxPerformance(self, n: int, speed: List[int], efficiency: List[int], k: int) -> int:
people = sorted(zip(speed, efficiency), key = lambda tup : -tup[1]) # (spd, eff) in efficiency decreasing order
pq = []
spd_sum = 0
best_perf = 0 # ans
for idx, (spd, eff) in enumerate(people):
# init
if idx < k:
heapq.heappush(pq, spd)
spd_sum += spd
# replace min spd with higher spd, but with lower eff (this may result in lower or even 'higher' performance)
elif spd > pq[0]:
spd_sum = spd_sum + spd - heapq.heappushpop(pq, spd)
best_perf = max(best_perf, spd_sum * eff)
MODULO = int(1e9 + 7) # int 안씌우면 double 처리돼서 % 연산 시 오차 발생
return best_perf % MODULO

+ Recent posts