I came across a coding challenge on the internet the question is listed below:
Have the function FoodDistribution(arr) read the array of numbers stored in arr which will represent the hunger level of different people ranging from 0 to 5 (0 meaning not hungry at all, 5 meaning very hungry). You will also have N sandwiches to give out which will range from 1 to 20. The format of the array will be [N, h1, h2, h3, ...] where N represents the number of sandwiches you have and the rest of the array will represent the hunger levels of different people. Your goal is to minimize the hunger difference between each pair of people in the array using the sandwiches you have available.
For example: if arr is [5, 3, 1, 2, 1], this means you have 5 sandwiches to give out. You can distribute them in the following order to the people: 2, 0, 1, 0. Giving these sandwiches to the people their hunger levels now become: [1, 1, 1, 1]. The difference between each pair of people is now 0, the total is also 0, so your program should return 0. Note: You may not have to give out all, or even any, of your sandwiches to produce a minimized difference.
Another example: if arr is [4, 5, 2, 3, 1, 0] then you can distribute the sandwiches in the following order: [3, 0, 1, 0, 0] which makes all the hunger levels the following: [2, 2, 2, 1, 0]. The differences between each pair of people is now: 0, 0, 1, 1 and so your program should return the final minimized difference of 2.
My first approach was to try to solve it greedily as the following:
- Loop until the sandwiches are zero
- For each element in the array copy the array and remove one hunger at location i
- Get the best combination that will give you the smallest hunger difference
- Reduce the sandwiches by one and consider the local min as the new hunger array
- Repeat until sandwiches are zero or the hunger difference is zero
I thought when taking the local minimum it led to the global minimum which was wrong based on the following use case [7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5]
def FoodDistribution(arr):
sandwiches = arr[0]
hunger_levels = arr[1:]
# Function to calculate the total difference
def total_difference(hunger_levels):
return sum(abs(hunger_levels[i] - hunger_levels[i + 1]) for i in range(len(hunger_levels) - 1))
def reduce_combs(combs):
local_min = float('inf')
local_min_comb = None
for comb in combs:
current_difference = total_difference(comb)
if current_difference < local_min:
local_min = current_difference
local_min_comb = comb
return local_min_comb
# Function to distribute sandwiches
def distribute_sandwiches(sandwiches, hunger_levels):
global_min = total_difference(hunger_levels)
print(global_min)
while sandwiches > 0 and global_min > 0:
combs = []
for i in range(len(hunger_levels)):
comb = hunger_levels[:]
comb[i] -= 1
combs.append(comb)
local_min_comb = reduce_combs(combs)
x = total_difference(local_min_comb)
print( sandwiches, x, local_min_comb)
global_min = min(global_min, x)
hunger_levels = local_min_comb
sandwiches -= 1
return global_min
# Distribute sandwiches and calculate the minimized difference
global_min = distribute_sandwiches(sandwiches, hunger_levels)
return global_min
if __name__ == "__main__":
print(FoodDistribution([7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5]))
I changed my approach to try to brute force and then use memorization to optimize the time complexity
- Recurse until out of bounds or sandwiches are zero
- For each location there are two options either to use a sandwich or ignore
- When the option is to use a sandwich decrement sandwiches by one and stay at the same index.
- When the option is to ignore increment the index by one.
- Take the minimum between the two options and return it.
The issue here is that I didn't know what to store in the memo and storing the index and sandwiches is not enough. I am not sure if this problem has a better complexity than 2^(n+s). Is there a way to know if dynamic programming or memorization is not the way to solve the problem and in this case can I improve the complexity by memorization or does this problem need to be solved with a different approach?
def FoodDistribution(arr):
sandwiches = arr[0]
hunger_levels = arr[1:]
# Distribute sandwiches and calculate the minimized difference
global_min = solve(0, sandwiches, hunger_levels)
return global_min
def solve(index, sandwiches, hunger_levels):
if index >= len(hunger_levels) or sandwiches == 0:
return total_difference(hunger_levels)
# take a sandwich
hunger_levels[index] += -1
sandwiches += -1
minTake = solve(index, sandwiches, hunger_levels)
hunger_levels[index] += 1
sandwiches += 1
# dont take sandwich
dontTake = solve(index + 1, sandwiches, hunger_levels)
return min(minTake, dontTake)
def total_difference(hunger_levels):
return sum(abs(hunger_levels[i] - hunger_levels[i + 1]) for i in range(len(hunger_levels) - 1))
if __name__ == "__main__":
print(FoodDistribution([7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5]))
Edit: Multiple states will give you the optimal answer for the use case above
sandwiches = 7
hunger = [5, 4, 3, 4, 5, 2, 3, 1, 4, 5]
optimal is 6
states as follow
[3, 3, 3, 3, 3, 2, 2, 1, 4, 5]
[4, 3, 3, 3, 3, 2, 2, 1, 4, 4]
[4, 4, 3, 3, 2, 2, 2, 1, 4, 4]
[4, 4, 3, 3, 3, 2, 1, 1, 4, 4]
[4, 4, 3, 3, 3, 2, 2, 1, 3, 4]
[4, 4, 3, 3, 3, 2, 2, 1, 4, 4]
[5, 4, 3, 3, 3, 2, 2, 1, 3, 3]
Note: I accepted @Matt Timmermans answer as it provides the best time complexity n and nlogn. But the two other answer are amazing and good to understand and be able to implement the solution using dynamic programming or memorization. Personally I prefer the memorization version expected time complexity is snh where h is the max hunger level in the array.