2528. Maximize the Minimum Powered City
Problem Description
In this problem, we are given an array stations
that represents the number of power stations in each city of a series of cities indexed from 0
to n-1
. Every power station has a range r
that determines the cities it can supply power to. A power station in city i
can supply power to any city j
such that the absolute difference |i - j|
is less than or equal to r
.
The power of a city is defined as the sum of power stations providing power to it, considering the range of the power stations. We are given a number k
representing the number of additional power stations that can be built.
Our task is to find the maximum possible minimum power across all cities after building k
additional power stations in the most optimal manner. In simpler terms, we want to increase the minimum power of the cities to the highest possible value by strategically placing the new power stations.
Intuition
To solve this problem, we're going to use a binary search algorithm that tries to find the minimum power value that can be achieved after optimally placing k
new power stations. Here's the intuition behind the approach:
-
Defining the Search Space: The search space will be between 0 and a maximum possible value, which is a large number since the exact upper limit isn't clearly defined in the problem.
-
Binary Search: Within this search space, we can use binary search to find the maximum possible minimum power. For each attempt (mid value in binary search), we check if we can achieve this minimum power by optimally placing
k
new power stations. -
Feasibility Check: The
check
function determines if a given minimum powerx
can be achieved usingk
additional power stations. This involves, for each city, checking if the existing power plus the power from the new stations meets the target powerx
. If a city does not meet the target, we add more stations within its range until it does, without exceeding the limit ofk
new stations.
The solution uses a greedy approach within the check function, always placing new power stations where they are needed most (where the deficit is greatest). If we ever require more than k
additional stations to meet the target power for all cities, we know the chosen target power x
is not feasible.
-
Adjusting the Binary Search: If we find that a certain target minimum power
x
is achievable, it means we might be able to achieve an even higher minimum power, so we go higher in our binary search. On the other hand, ifx
is not achievable, we need to try a lower value. -
Determining the Result: The binary search continues until the range has been narrowed down to the highest minimum power that can be achieved with
k
additional power stations. This value is then returned as the result.
Learn more about Greedy, Queue, Binary Search, Prefix Sum and Sliding Window patterns.
Solution Approach
The solution uses a combination of binary search and greedy algorithm principles to find the maximum possible minimum power of a city:
-
Initialization: We first create an additional list
d
of the same size as the number of citiesn
. Thisd
list is used to apply a difference array pattern which makes range updates efficient. -
Prepare Prefix Sum Array: We then convert
d
into a prefix sum arrays
by using theaccumulate
function from theitertools
module in Python. This prefix sum arrays
allows us to quickly calculate the power of each city from the existing stations. -
Binary Search Setup: A binary search is set up with the left (low) boundary as
0
and the right (high) boundary as a large number1 << 40
to account for the upper limit of the power that can be achieved. -
Binary Search Execution: In each iteration of binary search, we calculate the middle value
mid
between the currentleft
andright
boundaries, and we then check if it's possible to achieve this middle value as the minimum power, using thecheck
function. -
Check Function: The
check
function attempts to distribute thek
new power stations among the cities in a way that each city at least gets power equal tox
. It uses the strategy of greedily placing new power stations in the cities that are further away from the target power. A running totalt
represents the extra power provided on top of what the prefix sum arrays
provides. The function updates the difference arrayd
to account for the new stations, effectively spreading their range over the cities. -
Adjust the Search Range: If the
check
function confirms thatx
is achievable, theleft
(low) boundary of the search range is updated tomid
. If not, theright
(high) boundary is updated tomid - 1
. This narrows down the search space to find the highest minimum power that is achievable. -
Finding the Result: Once the binary search is completed, and the
left
andright
boundaries have converged, the solution returns theleft
boundary, which represents the maximum possible minimum power after the additionalk
power stations are optimally distributed.
The solution extensively uses the difference array pattern to efficiently handle range updates and queries by converting the initial array into a differential array and using a prefix sum to maintain the range updates. This combination with binary search solves the problem by repeatedly trying different power values until the highest possible minimum power is found to be achievable with k
new power stations.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider an example where the array stations
is [1,0,2,0,1]
, representing the number of power stations in five cities, and each power station has a range r
of 1
. Additionally, we are given k = 2
, meaning we can build 2 additional power stations.
-
Step 1: Initialization Begin with a difference array (which we'll call
d
) of the same size:[1, 0, 2, 0, 1]
. -
Step 2: Prepare Prefix Sum Array Convert
d
into a prefix sum arrays
. In our case,s
would be[1, 1, 3, 3, 4]
. This represents the total number of power stations affecting each city. -
Step 3: Binary Search Setup Set the search space:
left = 0
andright = 1 << 40
(a large number). -
Step 4 & 5: Binary Search Execution and Check Function Perform binary search:
- First Iteration:
mid = (left + right) / 2
is very large; assume it's larger than the sum of all power stations plusk
.- The
check
function will find that thismid
value is not achievable withk = 2
. - Set
right = mid - 1
.
- Second Iteration:
- Find a new
mid
. - Binary search narrows down to a reasonable guess, say
mid = 3
. - The
check
function tries to achieve at least3
power for each city with the aid ofk = 2
additional stations:- For city 0, already has power = 1; needs 2 more (deficit is 2).
- For city 1, already has power = 1; needs 2 more (deficit is 2).
- We place one additional station in city 1 (with range
r = 1
), augmenting the power of cities 0, 1, and 2. - Now, cities 0, 1, 2 all have power at least
3
, so no deficit. Remainingk = 1
. - For city 3, has power = 3; needs 0 more (no deficit).
- For city 4, has power = 4; already meets the requirement.
- The
check
function confirms that each city has a power of at least3
with 1 additional station remaining. This is achievable. - Set
left = mid
.
- Find a new
- First Iteration:
Continue the binary search adjusting left
and right
until they converge, finding the highest achievable minimum power value with the addition of k
stations.
- Step 6 & 7: Adjust the Search Range and Find the Result
Once the binary search is conclusive:
left
andright
converge to the maximum minimum power achievable. Let's say in our example, it converges at3
. So,3
is the maximum possible minimum power that can be achieved by optimally placing 2 additional power stations.
Thus, if the problem asked for the maximum possible minimum power of a city after building 2 additional power stations with a range of 1, our answer, in this case, would be 3
.
Solution Implementation
1from itertools import accumulate
2
3class Solution:
4 def max_power(self, stations: List[int], range_limit: int, power_units: int) -> int:
5 # Helper function to check if it's possible to achieve at least 'target_power' using
6 # at most 'available_units' of power units.
7 def is_feasible(target_power: int, available_units: int) -> bool:
8 delta = [0] * (total_stations + 1)
9 total_power = 0
10 for i in range(total_stations):
11 total_power += delta[i]
12 required_power = target_power - (current_power[i] + total_power)
13 if required_power > 0:
14 if available_units < required_power:
15 return False
16 available_units -= required_power
17 j = min(i + range_limit, total_stations - 1)
18 left, right = max(0, j - range_limit), min(j + range_limit, total_stations - 1)
19 delta[left] += required_power
20 delta[right + 1] -= required_power
21 total_power += required_power
22 return True
23
24 total_stations = len(stations)
25 delta = [0] * (total_stations + 1)
26
27 # Precompute the power at each station after applying the initial power units
28 for i, power in enumerate(stations):
29 left, right = max(0, i - range_limit), min(i + range_limit, total_stations - 1)
30 delta[left] += power
31 delta[right + 1] -= power
32 current_power = list(accumulate(delta))
33
34 # Binary search for the maximum power that can be achieved using given power units
35 left, right = 0, 1 << 40 # Using a large right value to ensure it's greater than the maximum achievable power
36 while left < right:
37 mid = (left + right + 1) >> 1 # Calculate mid and round up to ensure we don't get stuck in an infinite loop
38 if is_feasible(mid, power_units):
39 left = mid
40 else:
41 right = mid - 1
42
43 # The final answer, which is the maximum power that can be achieved
44 return left
45
1class Solution {
2 private long[] prefixSum; // To hold the running prefix sums of power values.
3 private long[] diff; // To represent the difference array for doing range updates.
4 private int stationCount; // The number of stations.
5
6 // Primary function to calculate the maximum power using binary search.
7 public long maxPower(int[] stations, int radius, int k) {
8 stationCount = stations.length;
9 diff = new long[stationCount + 1];
10 prefixSum = new long[stationCount + 1];
11 // Initialize the differencing array based on the power ranges of each station.
12 for (int i = 0; i < stationCount; ++i) {
13 int left = Math.max(0, i - radius), right = Math.min(i + radius, stationCount - 1);
14 diff[left] += stations[i];
15 diff[right + 1] -= stations[i];
16 }
17 // Construct the prefixSum array from the diff array.
18 prefixSum[0] = diff[0];
19 for (int i = 1; i < stationCount + 1; ++i) {
20 prefixSum[i] = prefixSum[i - 1] + diff[i];
21 }
22 // Binary search to determine the maximum power.
23 long left = 0, right = 1L << 40;
24 while (left < right) {
25 long mid = (left + right + 1) >>> 1; // unsigned division by 2.
26 // If the current power level is possible, explore higher power.
27 if (check(mid, radius, k)) {
28 left = mid;
29 } else {
30 // Otherwise, explore lower power.
31 right = mid - 1;
32 }
33 }
34 return left;
35 }
36
37 // Helper function to check if the given power level is attainable.
38 private boolean check(long power, int radius, int k) {
39 // Reset the difference array for this check.
40 Arrays.fill(diff, 0);
41 long tempSum = 0; // To keep track of the current increments.
42
43 for (int i = 0; i < stationCount; ++i) {
44 tempSum += diff[i];
45 // Calculate how much more power is needed to reach the "power" target.
46 long requiredPower = power - (prefixSum[i] + tempSum);
47 // If additional power is required.
48 if (requiredPower > 0) {
49 // Check if we have enough power bombs to meet the requirement.
50 if (k < requiredPower) {
51 return false;
52 }
53 // Use the power bombs to increase the power of the range.
54 k -= requiredPower;
55 // Update the effective range in the difference array.
56 int j = Math.min(i + radius, stationCount - 1);
57 int left = Math.max(0, j - radius);
58 int right = Math.min(j + radius, stationCount - 1);
59 diff[left] += requiredPower;
60 diff[right + 1] -= requiredPower;
61 // Update the current sum with the additional power.
62 tempSum += requiredPower;
63 }
64 }
65 // If we've made it through the loop, the power level is attainable.
66 return true;
67 }
68}
69
1#include <vector>
2#include <cstring> // for memset
3#include <algorithm> // for max and min functions
4
5class Solution {
6public:
7 long long maxPower(std::vector<int>& stations, int radius, int limit) {
8 int stationCount = stations.size();
9 std::vector<long long> delta(stationCount + 1, 0);
10 // Calculate the initial power distribution with the given stations
11 for (int i = 0; i < stationCount; ++i) {
12 int leftBoundary = std::max(0, i - radius);
13 int rightBoundary = std::min(i + radius, stationCount - 1);
14 delta[leftBoundary] += stations[i];
15 delta[rightBoundary + 1] -= stations[i];
16 }
17 // Compute the prefix sum to get the power at each station
18 std::vector<long long> prefixSum(stationCount + 1, 0);
19 prefixSum[0] = delta[0];
20 for (int i = 1; i <= stationCount; ++i) {
21 prefixSum[i] = prefixSum[i - 1] + delta[i];
22 }
23 // Lambda function to check if a certain power level x is achievable
24 auto checkFeasibility = [&](long long x, int remainingBoosts) {
25 std::vector<long long> adjustments(delta.size(), 0);
26 long long appliedAdjustment = 0;
27 for (int i = 0; i < stationCount; ++i) {
28 appliedAdjustment += adjustments[i];
29 long long shortfall = x - (prefixSum[i] + appliedAdjustment);
30 if (shortfall > 0) {
31 // If we don't have enough boosts to meet the shortfall, return false
32 if (remainingBoosts < shortfall) {
33 return false;
34 }
35 // Allocate boosts to cover the shortfall
36 remainingBoosts -= shortfall;
37 int boostEnd = std::min(i + radius, stationCount - 1);
38 int adjLeft = std::max(0, boostEnd - radius);
39 int adjRight = std::min(boostEnd + radius, stationCount - 1);
40 adjustments[adjLeft] += shortfall;
41 adjustments[adjRight + 1] -= shortfall;
42 appliedAdjustment += shortfall;
43 }
44 }
45 return true;
46 };
47 // Binary search to find the maximum power level achievable
48 long long low = 0, high = 1e12;
49 while (low < high) {
50 long long mid = (low + high + 1) >> 1;
51 if (checkFeasibility(mid, limit)) {
52 low = mid; // If achievable, we can go higher
53 } else {
54 high = mid - 1; // Otherwise, we need to go lower
55 }
56 }
57 return low;
58 }
59};
60
1// Importing required functions from lodash
2import { max, min } from 'lodash';
3
4// Function to compute the maximum power possible given stations, radius, and limit
5function maxPower(stations: number[], radius: number, limit: number): number {
6 const stationCount: number = stations.length;
7 const delta: number[] = new Array(stationCount + 1).fill(0);
8
9 // Calculate the initial power distribution with the given stations
10 for (let i = 0; i < stationCount; ++i) {
11 const leftBoundary = max([0, i - radius]);
12 const rightBoundary = min([i + radius, stationCount - 1]);
13 delta[leftBoundary] += stations[i];
14 delta[rightBoundary + 1] -= stations[i];
15 }
16
17 // Compute the prefix sum to get the power at each station
18 const prefixSum: number[] = new Array(stationCount + 1).fill(0);
19 prefixSum[0] = delta[0];
20 for (let i = 1; i <= stationCount; ++i) {
21 prefixSum[i] = prefixSum[i - 1] + delta[i];
22 }
23
24 // Lambda function to check if a certain power level `x` is achievable
25 const checkFeasibility = (x: number, remainingBoosts: number): boolean => {
26 const adjustments: number[] = new Array(delta.length).fill(0);
27 let appliedAdjustment: number = 0;
28 for (let i = 0; i < stationCount; ++i) {
29 appliedAdjustment += adjustments[i];
30 const shortfall = x - (prefixSum[i] + appliedAdjustment);
31 if (shortfall > 0) {
32 // If we don't have enough boosts to meet the shortfall, return false
33 if (remainingBoosts < shortfall) {
34 return false;
35 }
36 // Allocate boosts to cover the shortfall
37 remainingBoosts -= shortfall;
38 const boostEnd = min([i + radius, stationCount - 1]);
39 const adjLeft = max([0, boostEnd - radius]);
40 const adjRight = min([boostEnd + radius, stationCount - 1]);
41 adjustments[adjLeft] += shortfall;
42 adjustments[adjRight + 1] -= shortfall;
43 appliedAdjustment += shortfall;
44 }
45 }
46 return true;
47 };
48
49 // Binary search to find the maximum power level achievable
50 let low: number = 0;
51 let high: number = 1e12;
52 while (low < high) {
53 const mid: number = Math.floor((low + high + 1) / 2);
54 if (checkFeasibility(mid, limit)) {
55 low = mid; // If achievable, we can go higher
56 } else {
57 high = mid - 1; // Otherwise, we need to go lower
58 }
59 }
60 return low;
61}
62
63// Example usage
64const result = maxPower([1, 2, 3], 1, 2);
65console.log(result);
66
Time and Space Complexity
Time Complexity
The time complexity of the code is determined by the operations performed in each function. Let's break them down:
-
The
check
function: It has a loop that runsn
times, wheren
is the number of stations. Within the loop, there are constant-time operations except for the calculations forj
,left
, andright
. These are also constant-time since they involve arithmetic operations. The inner loop essentially simulates the processr
times in the worst case for each station. So the complexity for thecheck
function isO(n)
. -
The binary search loop: The code uses a binary search to find the maximum power
x
. This binary search runs untilleft
becomes equal toright
. In each iteration, it calls thecheck
function which hasO(n)
complexity. The binary search will at most executelog2(1 << 40)
times, which simplifies tolog2(2^40) = 40
times. Hence, overall the time complexity for this part isO(n * logC)
, wherelogC
represents the logarithm of the constant1 << 40
, which is the upper bound of the search space.
Therefore, the total time complexity of the code is O(n * logC)
.
Space Complexity
The space complexity mainly involves space for the list d
of length n + 1
, the accumulated sum list s
, and some constant space for variables. We do not count the input size towards space complexity. Therefore, the space complexity is O(n)
.
In summary, the time complexity is O(n * logC)
and the space complexity is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Want a Structured Path to Master System Design Too? Don’t Miss This!