1. Problem Statement
In Simple Words: Imagine you have a straight road with several barns (stalls) located at specific coordinates. You also have a specific number of cows. These cows are very aggressive. If you put them too close to each other, they will fight. Your goal is to place all the cows into the stalls in such a way that the distance between the two closest cows is as large as possible.
Original Problem Statement:
Given an array of length , where each element denotes the coordinate of a stall, and an integer representing the number of aggressive cows, assign the cows to the stalls such that the minimum distance between any two cows is maximized.
Input Format:
- An integer array
stalls[]of size representing the coordinates of the stalls. - An integer representing the number of cows.
Output Format:
- A single integer representing the maximum possible minimum distance between any two cows.
Constraints:
2. Examples
Let's look at a few examples to understand how the placement works in practice.
Example 1
Input: stalls = [1, 2, 8, 4, 9], C = 3
Output: 3
Step-by-Step Explanation:
- First, sort the stalls:
[1, 2, 4, 8, 9]. - We need to place 3 cows.
- Let's try placing them at indices 0, 2, and 3 (coordinates 1, 4, 8).
- The distances between adjacent cows are:
- Between 1 and 4:
- Between 4 and 8:
- The minimum of these distances is 3. We cannot achieve a minimum distance greater than 3. For instance, if we placed them at 1, 4, and 9, the minimum distance is still 3.
Example 2
Input: stalls = [1, 2, 4, 8, 9], C = 2
Output: 8
Step-by-Step Explanation:
- The stalls are already sorted.
- We only need to place 2 cows. To maximize their distance, we put them at the two extremes.
- Place cow 1 at coordinate 1.
- Place cow 2 at coordinate 9.
- The distance is .
Example 3 (Edge Case)
Input: stalls = [1, 10], C = 2
Output: 9
Step-by-Step Explanation:
- Only two stalls and two cows.
- Cow 1 goes to stall 1. Cow 2 goes to stall 10.
- Distance is . This shows that the answer can simply be the difference between the maximum and minimum elements if .
3. Key Observations
Before jumping into solutions, we need to extract the hidden truths of the problem:
- Order Matters (Sorting is mandatory): Distance is calculated between adjacent elements on a 1D line. Without sorting, evaluating the distance between physically adjacent stalls is impossible. Sorting brings spatial order to the data.
- "Maximizing the Minimum": Whenever an interview question asks you to maximize a minimum value (or minimize a maximum value), it is a massive hint that you will be dealing with a Binary Search on Answer pattern.
- The Greedy Choice: If you want to place cows as far apart as possible, it always makes sense to put the very first cow in the very first stall. By doing this, you leave the maximum possible amount of runway on the right side to accommodate the remaining cow
4. Intuition
Let's build the intuition without immediately throwing technical jargon at the problem.
Why is checking every combination inefficient?
Imagine choosing stalls out of stalls. Mathematically, this is " choose " or . For , checking every single arrangement of cows is astronomically large. We need a way to bypass combinations altogether.
Why does the answer lie within a fixed range?
Think about the possible distances:
- What is the absolute smallest distance between any two cows? Assuming stalls can be adjacent, the minimum possible distance is 1 (or the smallest difference between any two sorted stalls).
- What is the absolute largest distance? The distance between the first stall and the last stall.
Therefore, our answer must be a number between and .
Feasibility Checking (The "Can we do it?" question)
Instead of asking "What is the max distance?", what if we asked a simpler question:
"Given a specific distance , is it possible to place all cows such that every cow is at least units apart?"
This is much easier to solve! We can just walk through the sorted stalls and place a cow whenever we find a stall that is units away from the last cow.
The Monotonic Magic
Let's say our possible distances range from 1 to 10. We ask our feasibility question for each:
- Distance 1? Yes, easily.
- Distance 2? Yes.
- Distance 3? Yes.
- Distance 4? No, the stalls are too cramped.
- Distance 5? No.
The answers always follow a pattern: [Yes, Yes, Yes, No, No, No].
The moment the answer becomes "No", it will forever stay "No" for larger distances. We are looking for the last "Yes". Whenever you have a monotonic function (a sequence of all trues followed by all falses), you can use Binary Search to find the boundary in time!
5. Brute Force Solution
Thought Process
If we don't know binary search, how do we solve it? We know the answer lies between 1 and . We can simply start checking from distance and keep going up by 1. For every distance, we check if it's possible to place the cows. The last distance that returns true is our answer.
Algorithm
- Sort the
stallsarray. - Find
max_distance=stalls[N-1] - stalls[0]. - Loop
dfrom 1 tomax_distance. - For each
d, use a helper functioncanPlaceCows(stalls, C, d)to check if placement is valid. - If
canPlaceCowsreturnsfalse, it means distancedis impossible. Because we are increasingdone by one, the previous valid distanced-1must be the maximum possible minimum distance. - Return
d-1.
CPP CODE:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Helper function to check if placement is possible
bool canPlaceCows(vector<int>& stalls, int C, int dist) {
int cowsPlaced = 1; // Place the first cow
int lastPos = stalls[0]; // at the very first stall
for (int i = 1; i < stalls.size(); i++) {
if (stalls[i] - lastPos >= dist) {
cowsPlaced++; // Place the next cow
lastPos = stalls[i]; // Update the position of the last placed cow
if (cowsPlaced == C) return true; // We successfully placed all cows
}
}
return false;
}
int aggressiveCowsBruteForce(vector<int>& stalls, int C) {
sort(stalls.begin(), stalls.end());
int n = stalls.size();
int max_dist = stalls[n - 1] - stalls[0];
// Linear search on the answer space
for (int d = 1; d <= max_dist; d++) {
if (!canPlaceCows(stalls, C, d)) {
return d - 1;
}
}
return max_dist;
}Dry Run
- Stalls =
[1, 2, 4, 8, 9], C = 3. Max distance = 8. - Try : Can place at 1, 2, 4. (Valid)
- Try : Can place at 1, 4, 8. (Valid)
- Try : Can place at 1, 4, 8. (Valid)
- Try : Place at 1. Next is 8. Place at 8. Next doesn't exist. Only placed 2 cows. (Invalid)
- Since failed, answer is 3.
Complexity
- Time Complexity: for sorting + for the linear search. In the worst case, is , making this , which will result in a Time Limit Exceeded (TLE) error.
- Space Complexity: auxiliary space.
Drawbacks
The linear search is simply too slow when coordinates are large.
Optimal Solution (Binary Search on Answer)
Instead of a linear search, we use Binary Search on the range of possible answers.
Search Space
- Low:
1(The absolute minimum possible distance). - High:
stalls[N-1] - stalls[0](The maximum theoretical distance).
Why Binary Search is Valid
As established in the Intuition section, the feasibility predicate is monotonic. It yields a sequence like [True, True, True, False, False]. Binary search is perfect for finding the boundary between True and False in .
The Logic
- Calculate
mid = low + (high - low) / 2. - Check if we can place cows with at least
middistance. - If Yes (
canPlaceCowsis true): Thismidis a potential answer. We store it. However, we want to maximize this distance, so we abandon the smaller half and search the right half by settinglow = mid + 1. - If No (
canPlaceCowsis false): The distancemidis too large. We can't even place them this far apart, so we certainly can't place them further. We discard the right half by settinghigh = mid - 1.
8. Feasibility Function in Detail
The core engine of this optimal approach is the canPlaceCows function. Let's break it down:
bool canPlaceCows(vector<int>& stalls, int C, int dist) {
int cowsPlaced = 1;
int lastPos = stalls[0];
for (int i = 1; i < stalls.size(); i++) {
if (stalls[i] - lastPos >= dist) {
cowsPlaced++;
lastPos = stalls[i];
if (cowsPlaced == C) return true;
}
}
return false;
}- Why sorting is necessary: The
forloop sweeps from left to right. This only works ifstalls[i]is strictly greater thanlastPos. If the array wasn't sorted, we'd be jumping back and forth across the number line. - Why the first cow is at
stalls[0]: This is the greedy choice. By taking the leftmost slot, you consume the least amount of "number line real estate", leaving the maximum possible space for the remaining cows. - Why placing earlier never hurts: If you decide to put the first cow in
stalls[1]instead, you are just shrinking your available search space on the right for absolutely no benefit.
Algorithm
- Sort the array
stalls. - Define the search space:
low = 1high = stalls[N-1] - stalls[0]
- Initialize a variable
ans = 0. - While
low <= high:- Calculate
mid = low + (high - low) / 2. - Check if
canPlaceCows(stalls, C, mid)is true. - If true: Update
ans = midand search right (low = mid + 1). - If false: Search left (
high = mid - 1).
- Calculate
- Return
ans.
C++ Code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
private:
// Feasibility function to check if we can place C cows with at least 'dist' gap
bool canPlaceCows(const vector<int>& stalls, int cows, int dist) {
int cowsPlaced = 1; // Always place the first cow
int lastPos = stalls[0]; // at the very first stall
for (int i = 1; i < stalls.size(); i++) {
// If the current stall is far enough from the last placed cow
if (stalls[i] - lastPos >= dist) {
cowsPlaced++;
lastPos = stalls[i];
// If we've successfully placed all cows, return true early
if (cowsPlaced == cows) {
return true;
}
}
}
// Could not place all cows with the given minimum distance
return false;
}
public:
int aggressiveCows(vector<int>& stalls, int k) {
// Step 1: Sort the array to process stalls linearly
sort(stalls.begin(), stalls.end());
int n = stalls.size();
int low = 1;
int high = stalls[n - 1] - stalls[0];
int ans = 0;
// Step 2: Binary Search on the Answer space
while (low <= high) {
int mid = low + (high - low) / 2;
if (canPlaceCows(stalls, k, mid)) {
// If possible, store answer and look for a LARGER distance
ans = mid;
low = mid + 1;
} else {
// If not possible, look for a SMALLER distance
high = mid - 1;
}
}
return ans;
}
};Here is the clean, interview-ready implementation:
9. Dry Run of the Optimal Solution
Let's dry run Binary Search.
stalls = [1, 2, 4, 8, 9],C = 3low = 1high = 9 - 1 = 8ans = 0
Iteration 1:
mid = 1 + (8 - 1) / 2 = 4canPlaceCows(4):- Cow 1 at
1. - Next needed: . Stall
8works. Cow 2 at8. - Next needed: . None left.
- Total cows placed: 2. Need 3. Result: False.
- Cow 1 at
- Decision: Distance 4 is too large.
high = mid - 1 = 3.
Iteration 2:
low = 1,high = 3.mid = 1 + (3 - 1) / 2 = 2canPlaceCows(2):- Cow 1 at
1. - Next needed: . Stall
4works. Cow 2 at4. - Next needed: . Stall
8works. Cow 3 at8. - Total cows placed: 3. Result: True.
- Cow 1 at
- Decision: Distance 2 is possible. Store
ans = 2. Try for larger distance.low = mid + 1 = 3.
Iteration 3:
low = 3,high = 3.mid = 3 + (3 - 3) / 2 = 3canPlaceCows(3):- Cow 1 at
1. - Next needed: . Stall
4works. Cow 2 at4. - Next needed: . Stall
8works. Cow 3 at8. - Total cows placed: 3. Result: True.
- Cow 1 at
- Decision: Distance 3 is possible. Update
ans = 3. Try for larger.low = mid + 1 = 4.
Termination:
low(4) is now greater thanhigh(3). Loop breaks.- Final Answer: 3.
Complexity Analysis
- Sorting Complexity: where is the number of stalls.
- Binary Search Complexity: The search space size is (let's call this ). Binary search takes iterations.
- Feasibility Check Complexity: In each binary search iteration, we run
canPlaceCows, which iterates through the array once, taking time. - Overall Time Complexity: . Given the constraints (, ), operations, which easily passes within the 1-second execution limit.
- Auxiliary Space Complexity: (ignoring the memory used by the sorting algorithm under the hood, which is typically in C++
std::sort).
Correctness Proof
Why are we absolutely sure this works?
- Greedy Correctness: Suppose an optimal configuration exists where the first cow is not at
stalls[0], but atstalls[1]. If we shift this cow back tostalls[0], the distance to the second cow strictly increases or remains the same. Shifting it left frees up space, meaning our greedy strategy of picking the leftmost valid stall will never invalidate a correct placement. - Binary Search Correctness: If is a valid minimum distance, it implies we can place cows at least apart. Consequently, any distance smaller than is also valid. If is invalid, any distance larger than must also be invalid. This forms a strict boolean sequence (Monotonicity) allowing binary search to flawlessly pinpoint the highest valid .
- No Answer Skipped: Because we only shrink our bounds after logically proving half the search space is useless, it is mathematically impossible to skip the optimal answer.
Pattern Recognition
How do you know an unseen problem uses this exact logic? Look for the "Binary Search on Answer" pattern.
Indicators:
- The problem asks you to maximize a minimum or minimize a maximum.
- The problem involves finding a threshold value (e.g., "least weight capacity", "minimum time").
- Given a proposed answer, it is much easier to check if it's valid (using a greedy approach) than it is to construct the optimal answer directly.
- The validity of answers forms a monotonic sequence (e.g.,
YYYYNNNNorNNNNYYYY).
Problem link: Aggressive Cows | Practice | GeeksforGeeks
video:
aggressive cows
