Campus Bikes
Medium · Heap / Priority Queue · Explanation and code example
Priority Queue
How it works
Instead of storing all worker-bike pairs upfront, we can use a priority queue that only tracks the current best option for each worker. When a bike is taken, we add the next best option for that worker to the queue. This reduces memory usage since we only need the closest available bike for each unassigned worker at any time.
Steps
- For each worker, sort their bike options by distance (and bike index as tiebreaker) in descending order, so we can pop the closest one efficiently.
- Add each worker's closest bike to a min-heap sorted by (distance, worker index, bike index).
- Pop the smallest element from the heap. If the bike is available, assign it to the worker.
- If the bike was taken, push the worker's next closest bike option onto the heap.
- Continue until all workers are assigned. Return the assignment array.
Complexity
- Time complexity:
O(N M log M) - Space complexity:
O(N M)
Where
Nis the number of workers, andMis the number of bikes.
Things to watch for
Incorrect Tie-Breaking Order
The problem requires breaking ties by worker index first, then bike index. Sorting by (distance, bike, worker) instead of (distance, worker, bike) produces wrong assignments.
Using Euclidean Distance Instead of Manhattan Distance
The problem specifically asks for Manhattan distance. Using Euclidean distance will give incorrect results.
Not Tracking Both Worker and Bike Availability
When a worker-bike pair is assigned, both must be marked as taken. Forgetting to check either condition leads to double assignments.
Python code example
from typing import List
import heapq
class Solution:
def assignBikes(self, workers: List[List[int]], bikes: List[List[int]]) -> List[int]:
def find_distance(worker_loc, bike_loc):
return abs(worker_loc[0] - bike_loc[0]) + abs(worker_loc[1] - bike_loc[1])
# List of triplets (distance, worker index, bike index) for each worker-bike combination
worker_to_bike_list = []
pq = []
for worker, worker_loc in enumerate(workers):
curr_worker_pairs = []
for bike, bike_loc in enumerate(bikes):
distance = find_distance(worker_loc, bike_loc)
curr_worker_pairs.append((distance, worker, bike))
# Sort the worker_to_bike_list for the current worker in reverse order
curr_worker_pairs.sort(reverse=True)
# Add the closest bike for this worker to the priority queue
heapq.heappush(pq, curr_worker_pairs.pop())
# Store the remaining options for the current worker in worker_to_bike_list
worker_to_bike_list.append(curr_worker_pairs)
# Initialize all values to false, to signify no bikes have been taken
bike_status = [False] * len(bikes)
# Initialize all values to -1, to signify no worker has a bike
worker_status = [-1] * len(workers)
while pq:
# Pop the worker-bike pair with smallest distance
distance, worker, bike = heapq.heappop(pq)
if not bike_status[bike]:
# If the bike is free, assign the bike to the worker
bike_status[bike] = True
worker_status[worker] = bike
else:
# Otherwise, add the next closest bike for the current worker to the priority queue
next_closest_bike = worker_to_bike_list[worker].pop()
heapq.heappush(pq, next_closest_bike)
return worker_status
C++ code example
class Solution {
public:
// Function to return the Manhattan distance
int distance(vector<int>& worker_loc, vector<int>& bike_loc) {
return abs(worker_loc[0] - bike_loc[0]) + abs(worker_loc[1] - bike_loc[1]);
}
vector<int> assignBikes(vector<vector<int>>& workers, vector<vector<int>>& bikes) {
// List of triplets (distance, worker, bike) for each bike corresponding to worker
vector<vector<tuple<int, int, int>>> workerToBikeList;
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>,
greater<tuple<int, int, int>>> pq;
for (int worker = 0; worker < workers.size(); worker++) {
// Add all the bikes with their distances from the current worker
vector<tuple<int, int, int>> currWorkerPairs;
for (int bike = 0; bike < bikes.size(); bike++) {
int dist = distance(workers[worker], bikes[bike]);
currWorkerPairs.push_back({dist, worker, bike});
}
// Sort the workerToBikeList for the current worker in reverse order.
sort(currWorkerPairs.begin(), currWorkerPairs.end(), greater<tuple<int, int, int>>());
// For each worker, add their closest bike to the priority queue
pq.push(currWorkerPairs.back());
// Second last bike is now the closest bike for this worker
currWorkerPairs.pop_back();
// Store the remaining options for the current worker in workerToBikeList
workerToBikeList.push_back(currWorkerPairs);
}
// Initialize all values to false, to signify no bikes have been taken
vector<int> bikeStatus(bikes.size(), false);
// Initialize all index to -1, to signify no worker has a bike
vector<int> workerStatus(workers.size(), -1);
while (!pq.empty()) {
// Pop the pair with smallest distance
auto[dist, worker, bike] = pq.top();
pq.pop();
bike = bike;
worker = worker;
if (!bikeStatus[bike]) {
// If the bike is free, assign the bike to the worker
bikeStatus[bike] = true;
workerStatus[worker] = bike;
} else {
// Otherwise, add the next closest bike for the current worker
pq.push(workerToBikeList[worker].back());
workerToBikeList[worker].pop_back();
}
}
return workerStatus;
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.