Taro Logo

Total Cost to Hire K Workers

Medium
MathWorks logo
MathWorks
2 views
Topics:
ArraysGreedy AlgorithmsTwo Pointers

You are given a 0-indexed integer array costs where costs[i] is the cost of hiring the ith worker.

You are also given two integers k and candidates. We want to hire exactly k workers according to the following rules:

  • You will run k sessions and hire exactly one worker in each session.
  • In each hiring session, choose the worker with the lowest cost from either the first candidates workers or the last candidates workers. Break the tie by the smallest index.
    • For example, if costs = [3,2,7,7,1,2] and candidates = 2, then in the first hiring session, we will choose the 4th worker because they have the lowest cost [3,2,7,7,1,2].
    • In the second hiring session, we will choose 1st worker because they have the same lowest cost as 4th worker but they have the smallest index [3,2,7,7,2]. Please note that the indexing may be changed in the process.
  • If there are fewer than candidates workers remaining, choose the worker with the lowest cost among them. Break the tie by the smallest index.
  • A worker can only be chosen once.

Return the total cost to hire exactly k workers.

Example 1:

Input: costs = [17,12,10,2,7,2,11,20,8], k = 3, candidates = 4
Output: 11
Explanation: We hire 3 workers in total. The total cost is initially 0.
- In the first hiring round we choose the worker from [17,12,10,2,7,2,11,20,8]. The lowest cost is 2, and we break the tie by the smallest index, which is 3. The total cost = 0 + 2 = 2.
- In the second hiring round we choose the worker from [17,12,10,7,2,11,20,8]. The lowest cost is 2 (index 4). The total cost = 2 + 2 = 4.
- In the third hiring round we choose the worker from [17,12,10,7,11,20,8]. The lowest cost is 7 (index 3). The total cost = 4 + 7 = 11. Notice that the worker with index 3 was common in the first and last four workers.
The total hiring cost is 11.

Example 2:

Input: costs = [1,2,4,1], k = 3, candidates = 3
Output: 4
Explanation: We hire 3 workers in total. The total cost is initially 0.
- In the first hiring round we choose the worker from [1,2,4,1]. The lowest cost is 1, and we break the tie by the smallest index, which is 0. The total cost = 0 + 1 = 1. Notice that workers with index 1 and 2 are common in the first and last 3 workers.
- In the second hiring round we choose the worker from [2,4,1]. The lowest cost is 1 (index 2). The total cost = 1 + 1 = 2.
- In the third hiring round there are less than three candidates. We choose the worker from the remaining workers [2,4]. The lowest cost is 2 (index 0). The total cost = 2 + 2 = 4.
The total hiring cost is 4.

Constraints:

  • 1 <= costs.length <= 105
  • 1 <= costs[i] <= 105
  • 1 <= k, candidates <= costs.length

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the constraints for the values of 'costs', 'k', and 'candidates'? Are there any upper or lower bounds I should be aware of?
  2. Can the 'costs' array contain negative cost values, or zero cost values?
  3. If multiple workers have the same minimum cost in a hiring session, you mention hiring the one with the smallest index. Could you clarify if the 'first candidates' and 'last candidates' groups overlap, and if so, how the selection process handles workers in the overlapping region with the lowest cost and smallest index?
  4. Is 'k' guaranteed to be less than or equal to the length of the 'costs' array?
  5. If 'candidates' is greater than or equal to the length of 'costs', how should the algorithm behave? Should I simply sort the entire array and take the 'k' smallest values?

Brute Force Solution

Approach

The brute force method finds the absolute lowest cost to hire K workers by checking every single possible combination of workers. We examine each possible group of workers, compute the total cost for that specific group, and ultimately pick the cheapest option we find.

Here's how the algorithm would work step-by-step:

  1. Consider every single possible group of K workers that we can select from the available pool of workers.
  2. For each group of K workers, calculate the total cost to hire them. This involves adding up the cost of each individual worker in that group.
  3. Keep track of the minimum cost we have found so far. Initially, this might be a very large number or considered infinity.
  4. As we calculate the cost for each group, compare it to our current minimum cost.
  5. If the cost of the current group is lower than the current minimum cost, we update the minimum cost to be the cost of this group.
  6. After checking every possible group of K workers, the final minimum cost we have tracked will be the absolute lowest cost to hire K workers.

Code Implementation

def min_cost_to_hire_k_workers_brute_force(costs, k_workers):
    number_of_workers = len(costs)
    minimum_total_cost = float('inf')

    # Iterate through all possible combinations of workers.
    for i in range(1 << number_of_workers):
        selected_workers = []
        for j in range(number_of_workers):
            if (i >> j) & 1:
                selected_workers.append(j)

        # Only process combinations of size k.
        if len(selected_workers) == k_workers:

            current_total_cost = 0
            # Calculate cost of the current worker group.
            for worker_index in selected_workers:
                current_total_cost += costs[worker_index]

            # Update minimum cost if necessary
            minimum_total_cost = min(minimum_total_cost, current_total_cost)

    return minimum_total_cost

Big(O) Analysis

Time Complexity
O(n choose k)The brute force method considers all possible combinations of selecting K workers from a pool of N workers. This means we need to examine every possible subset of size K. The number of such subsets is given by the binomial coefficient 'n choose k', which is calculated as n! / (k! * (n-k)!). Therefore, the time complexity is directly proportional to the number of these combinations. Since the algorithm iterates through each combination to calculate its cost, the overall time complexity is O(n choose k).
Space Complexity
O(1)The brute force approach, as described, calculates the cost for each group of K workers and updates the minimum cost found so far. It primarily uses a single variable to store the current minimum cost. Because the space used for storing the minimum cost and potentially a few loop counters doesn't scale with the input size (N, the total number of workers), the auxiliary space remains constant. No additional data structures are created that grow proportionally with the input size.

Optimal Solution

Approach

The trick here is to avoid checking every single combination of workers. We'll use a 'smart' selection process to pick the cheapest workers from both ends of the list and adjust as we go.

Here's how the algorithm would work step-by-step:

  1. Imagine two groups of candidates: one from the beginning of the list and one from the end.
  2. Start by picking the `k` cheapest candidates from both groups.
  3. Compare the cheapest candidates from each group and hire the absolute cheapest one first.
  4. After hiring someone, replenish the group they came from with the next available candidate from that side, if any are left.
  5. Repeat the process of comparing and hiring the cheapest candidate until you've hired `k` workers total.
  6. The total cost will be the sum of the cost of each candidate that you have hired.

Code Implementation

import heapq

def total_cost_to_hire_workers(costs, number_of_workers, number_of_candidates):
    total_cost = 0
    left_group = []
    right_group = []
    left_index = 0
    right_index = len(costs) - 1

    # Initial population of the priority queues.
    for _ in range(number_of_candidates):
        if left_index <= right_index:
            heapq.heappush(left_group, costs[left_index])
            left_index += 1
        else:
            break

    for _ in range(number_of_candidates):
        if left_index <= right_index:
            heapq.heappush(right_group, costs[right_index])
            right_index -= 1
        else:
            break

    for _ in range(number_of_workers):
        # Deciding which group to hire from.
        if not right_group or (left_group and left_group[0] <= right_group[0]):

            cheapest_worker = heapq.heappop(left_group)
            total_cost += cheapest_worker

            # Replenish from the left side.
            if left_index <= right_index:
                heapq.heappush(left_group, costs[left_index])
                left_index += 1

        else:
            # Replenish from the right side.
            cheapest_worker = heapq.heappop(right_group)
            total_cost += cheapest_worker
            if left_index <= right_index:
                heapq.heappush(right_group, costs[right_index])
                right_index -= 1

    return total_cost

Big(O) Analysis

Time Complexity
O(n log k)The core idea is to maintain two priority queues (heaps) of size at most k, one for the beginning and one for the end of the costs array. Initially, filling these heaps takes O(k log k) time for each, resulting in O(2 * k log k) which simplifies to O(k log k). We then iterate k times, each time extracting the minimum from the two heaps (O(log k)) and potentially adding a new element to the heap from the relevant side (also O(log k)). The extraction and addition are both done k times in a loop. Thus, the loop contributes O(k log k) time. In the case where n < 2k the time complexity to initially populate the heaps will be O(n log n). Given that k is typically smaller than n, the overall complexity is dominated by filling the initial queues and the iterative process of extraction, which gives a final complexity of O(n log k), in the worst case, when we exhaust the list on one side. Note that without the heap the time complexity would be O(n*k), by checking the min each time from a list of size k.
Space Complexity
O(k)The solution uses two heaps (or priority queues), one for the candidates from the beginning and one from the end of the costs array. In the worst case, each heap can hold up to k elements before workers are hired. Therefore, the auxiliary space is proportional to the number of workers to be hired, k. The extra space used by these data structures is thus O(k).

Edge Cases

CaseHow to Handle
Empty costs arrayReturn 0 immediately as there are no workers to hire.
k is 0Return 0 immediately as no workers need to be hired.
costs array size is less than candidatesTreat candidates as the size of the costs array; hire workers from the entire array.
costs array size equals kSum all costs in the array and return the result, as all workers must be hired.
All costs are identicalThe algorithm should still function correctly, picking the worker with the smallest index in case of ties.
Costs contain large integer values that could cause overflowUse long long to accumulate the total cost to prevent potential integer overflow.
candidates is larger than the array size divided by 2Handle this condition by hiring from the entire list if candidates is too big.
k is larger than the array sizeReturn the sum of all costs, as you have to hire everyone.