Before attempting this problem, you should be comfortable with:
We need to assign bikes to workers based on priority: smallest Manhattan distance first, then smallest worker index, then smallest bike index. By generating all possible worker-bike pairs with their distances and sorting them by these criteria, we can greedily assign each pair in order, skipping pairs where either the worker or bike is already taken.
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])
# Calculate the distance between each worker and bike.
all_triplets = []
for worker, worker_loc in enumerate(workers):
for bike, bike_loc in enumerate(bikes):
distance = find_distance(worker_loc, bike_loc)
all_triplets.append((distance, worker, bike))
# Sort the triplets. By default, sorting will prioritize the
# tuple's first value, then second value, and finally the third value
all_triplets.sort()
# 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)
# Keep track of how many worker-bike pairs have been made
pair_count = 0
for distance, worker, bike in all_triplets:
# If both worker and bike are free, assign the bike to
# the worker and mark the bike as taken
if worker_status[worker] == -1 and not bike_status[bike]:
bike_status[bike] = True
worker_status[worker] = bike
pair_count += 1
# If all the workers have the bike assigned, we can stop
if pair_count == len(workers):
return worker_status
return worker_statusWhere is the number of workers, and is the number of bikes.
Since Manhattan distances on a 1000x1000 grid are bounded (maximum 1998), we can use bucket sort instead of comparison-based sorting. By grouping worker-bike pairs by their distance, we can process pairs in distance order without explicit sorting. Within each distance bucket, pairs are naturally ordered by their insertion order (worker index first, then bike index).
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])
min_dist = float('inf')
dist_to_pairs = collections.defaultdict(list)
for worker, worker_loc in enumerate(workers):
for bike, bike_loc in enumerate(bikes):
distance = find_distance(worker_loc, bike_loc)
dist_to_pairs[distance].append((worker, bike))
min_dist = min(min_dist, distance)
curr_dist = min_dist
# 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)
# Keep track of how many worker-bike pairs have been made
pair_count = 0
# While all workers have not been assigned a bike
while pair_count < len(workers):
for worker, bike in dist_to_pairs[curr_dist]:
if worker_status[worker] == -1 and not bike_status[bike]:
# If both worker and bike are free, assign them to each other
bike_status[bike] = True
worker_status[worker] = bike
pair_count += 1
curr_dist += 1
return worker_statusWhere is the number of workers, is the number of bikes, and is the maximum possible Manhattan distance of a worker/bike pair. In this problem, equals .
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.
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_statusWhere is the number of workers, and is the number of bikes.
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.
# Wrong: bike index takes priority over worker index
all_triplets.sort(key=lambda x: (x[0], x[2], x[1]))The problem specifically asks for Manhattan distance. Using Euclidean distance will give incorrect results.
# Wrong: Euclidean distance
distance = math.sqrt((w[0] - b[0])**2 + (w[1] - b[1])**2)When a worker-bike pair is assigned, both must be marked as taken. Forgetting to check either condition leads to double assignments.