2555. Maximize Win From Two Segments
Problem Description
You have prizes positioned along the X-axis. You're given an integer array prizePositions
that is sorted in non-decreasing order, where prizePositions[i]
represents the position of the i-th prize. Multiple prizes can exist at the same position.
You're also given an integer k
which represents the length of segments you can select.
Your task is to select two segments, each of length k
, to collect prizes. A prize is collected if its position falls within at least one of your selected segments (including the segment endpoints). The two segments you choose can overlap.
For example, if k = 2
, you could choose segments [1, 3]
and [2, 4]
. This would collect any prize at positions between 1 and 3 (inclusive) OR between 2 and 4 (inclusive).
The goal is to find the maximum number of prizes you can collect by optimally choosing your two segments.
Key Points:
- Each segment must have length exactly
k
- Segments can have any integer endpoints
- Segments can overlap
- A prize is collected if it falls within at least one segment
- You want to maximize the total number of prizes collected
Intuition
The key insight is that we need to choose two segments optimally. Let's think about this problem step by step.
First, since the prizes are already sorted by position, any segment of length k
starting at position p
will collect all prizes between positions p
and p + k
(inclusive). This means we can think of each segment as a contiguous subarray of prizes.
Now, for choosing two segments optimally, we can consider each possible position as the end of the second segment. For each ending position, we need to:
- Determine how many prizes the second segment collects
- Find the best possible first segment that doesn't interfere with maximizing the second segment
This leads us to a dynamic programming approach. We can precompute f[i]
which represents the maximum number of prizes we can collect using one segment from the first i
prizes. This helps us avoid recalculating the best first segment repeatedly.
When we're at position i
(considering it as part of the second segment), we can:
- Use binary search to find the leftmost position
j
whereprizePositions[j] >= prizePositions[i] - k
. This tells us the starting point of a segment of lengthk
that ends at or after positioni
. - The second segment collects
i - j
prizes (from indexj
toi-1
) - The first segment should be chosen optimally from prizes before index
j
, which is exactlyf[j]
- Total prizes =
f[j] + (i - j)
By iterating through all positions and tracking the maximum total, we find the optimal solution. We also update f[i]
at each step to maintain our precomputed values for future positions.
The beauty of this approach is that it avoids overlap issues naturally - by choosing the first segment from prizes before index j
, we ensure it doesn't interfere with counting prizes in the second segment.
Learn more about Binary Search and Sliding Window patterns.
Solution Approach
We implement the solution using dynamic programming combined with binary search.
Step 1: Initialize Data Structures
- Create an array
f
of sizen + 1
wheref[i]
stores the maximum number of prizes that can be collected using one segment from the firsti
prizes - Initialize
f[0] = 0
since no prizes can be collected from zero prizes - Initialize
ans = 0
to track the maximum total prizes we can collect with two segments
Step 2: Iterate Through Each Prize Position We enumerate through each prize position using 1-based indexing (for cleaner DP transitions):
for i, x in enumerate(prizePositions, 1):
Step 3: Find the Left Boundary Using Binary Search
For each prize at position x
, we need to find the leftmost prize that would be included in a segment of length k
ending at or after position x
:
j = bisect_left(prizePositions, x - k)
This finds the smallest index j
where prizePositions[j] >= x - k
. The segment [x - k, x]
would collect all prizes from index j
to i - 1
.
Step 4: Calculate Maximum Prizes
- The second segment (ending at current position) collects
i - j
prizes - The first segment should be chosen optimally from the first
j
prizes, which gives usf[j]
prizes - Update the answer:
ans = max(ans, f[j] + i - j)
Step 5: Update DP Array
We update f[i]
for future iterations:
f[i] = max(f[i - 1], i - j)
This means the maximum prizes we can get from the first i
prizes is either:
- The maximum from the first
i - 1
prizes (not including current prize in our segment) - Or
i - j
prizes (using a segment that includes the current prize)
Step 6: Return Result
After processing all positions, ans
contains the maximum number of prizes we can collect with two segments.
Time Complexity: O(n log n)
where n
is the number of prizes. We iterate through each prize once and perform a binary search for each.
Space Complexity: O(n)
for the DP array f
.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example:
prizePositions = [1, 1, 2, 2, 3, 3, 5]
k = 2
We want to find two segments of length 2 that collect the maximum number of prizes.
Initialization:
n = 7
(number of prizes)f = [0, 0, 0, 0, 0, 0, 0, 0]
(size n+1)ans = 0
Iteration 1: i=1, x=1
- Binary search: Find leftmost position where prize ≥ (1-2) = -1
j = 0
(all prizes are ≥ -1)- Second segment [−1, 1] collects prizes at indices 0 to 0:
i - j = 1 - 0 = 1
prize - First segment from first j=0 prizes:
f[0] = 0
prizes - Total:
0 + 1 = 1
- Update:
ans = max(0, 1) = 1
- Update:
f[1] = max(f[0], 1 - 0) = max(0, 1) = 1
Iteration 2: i=2, x=1
- Binary search: Find leftmost position where prize ≥ (1-2) = -1
j = 0
- Second segment [−1, 1] collects prizes at indices 0 to 1:
i - j = 2 - 0 = 2
prizes - First segment:
f[0] = 0
prizes - Total:
0 + 2 = 2
- Update:
ans = max(1, 2) = 2
- Update:
f[2] = max(f[1], 2 - 0) = max(1, 2) = 2
Iteration 3: i=3, x=2
- Binary search: Find leftmost position where prize ≥ (2-2) = 0
j = 0
(prize at index 0 is 1 ≥ 0)- Second segment [0, 2] collects prizes at indices 0 to 2:
i - j = 3 - 0 = 3
prizes - First segment:
f[0] = 0
prizes - Total:
0 + 3 = 3
- Update:
ans = max(2, 3) = 3
- Update:
f[3] = max(f[2], 3 - 0) = max(2, 3) = 3
Iteration 4: i=4, x=2
- Binary search: Find leftmost position where prize ≥ (2-2) = 0
j = 0
- Second segment [0, 2] collects prizes at indices 0 to 3:
i - j = 4 - 0 = 4
prizes - First segment:
f[0] = 0
prizes - Total:
0 + 4 = 4
- Update:
ans = max(3, 4) = 4
- Update:
f[4] = max(f[3], 4 - 0) = max(3, 4) = 4
Iteration 5: i=5, x=3
- Binary search: Find leftmost position where prize ≥ (3-2) = 1
j = 0
(prize at index 0 is 1 ≥ 1)- Second segment [1, 3] collects prizes at indices 0 to 4:
i - j = 5 - 0 = 5
prizes - First segment:
f[0] = 0
prizes - Total:
0 + 5 = 5
- Update:
ans = max(4, 5) = 5
- Update:
f[5] = max(f[4], 5 - 0) = max(4, 5) = 5
Iteration 6: i=6, x=3
- Binary search: Find leftmost position where prize ≥ (3-2) = 1
j = 0
- Second segment [1, 3] collects prizes at indices 0 to 5:
i - j = 6 - 0 = 6
prizes - First segment:
f[0] = 0
prizes - Total:
0 + 6 = 6
- Update:
ans = max(5, 6) = 6
- Update:
f[6] = max(f[5], 6 - 0) = max(5, 6) = 6
Iteration 7: i=7, x=5
- Binary search: Find leftmost position where prize ≥ (5-2) = 3
j = 4
(prize at index 4 is 3 ≥ 3)- Second segment [3, 5] collects prizes at indices 4 to 6:
i - j = 7 - 4 = 3
prizes - First segment from first j=4 prizes:
f[4] = 4
prizes - Total:
4 + 3 = 7
- Update:
ans = max(6, 7) = 7
- Update:
f[7] = max(f[6], 7 - 4) = max(6, 3) = 6
Final Answer: 7
The optimal solution uses:
- First segment: [0, 2] collecting prizes at positions 1, 1, 2, 2 (4 prizes)
- Second segment: [3, 5] collecting prizes at positions 3, 3, 5 (3 prizes)
- Total: 7 prizes
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4class Solution:
5 def maximizeWin(self, prizePositions: List[int], k: int) -> int:
6 n = len(prizePositions)
7
8 # dp[i] stores the maximum number of prizes that can be collected
9 # from the first i positions using one segment
10 max_prizes_up_to = [0] * (n + 1)
11
12 # Track the maximum number of prizes using two segments
13 max_total_prizes = 0
14
15 # Iterate through each position as a potential end of the second segment
16 for i, current_pos in enumerate(prizePositions, 1):
17 # Find the leftmost position where prizes can be collected
18 # if the current position is the rightmost position of a segment of length k
19 left_bound_idx = bisect_left(prizePositions, current_pos - k)
20
21 # Calculate maximum prizes using:
22 # - First segment: best result up to left_bound_idx
23 # - Second segment: prizes from left_bound_idx to current position i
24 prizes_in_current_segment = i - left_bound_idx
25 max_total_prizes = max(max_total_prizes,
26 max_prizes_up_to[left_bound_idx] + prizes_in_current_segment)
27
28 # Update dp array: maximum prizes using one segment up to position i
29 max_prizes_up_to[i] = max(max_prizes_up_to[i - 1], prizes_in_current_segment)
30
31 return max_total_prizes
32
1class Solution {
2 /**
3 * Finds the maximum number of prizes that can be collected using two segments of length k.
4 *
5 * @param prizePositions sorted array of prize positions
6 * @param k the maximum length of each segment
7 * @return maximum number of prizes that can be collected
8 */
9 public int maximizeWin(int[] prizePositions, int k) {
10 int n = prizePositions.length;
11
12 // dp[i] stores the maximum prizes that can be collected
13 // from the first i prizes using one segment
14 int[] dp = new int[n + 1];
15
16 int maxPrizes = 0;
17
18 // Iterate through each position as the end of the second segment
19 for (int i = 1; i <= n; i++) {
20 int currentPosition = prizePositions[i - 1];
21
22 // Find the leftmost position where prizes can be collected
23 // for a segment ending at current position with length k
24 int leftBoundary = binarySearch(prizePositions, currentPosition - k);
25
26 // Calculate prizes in current segment: from leftBoundary to i-1
27 int currentSegmentPrizes = i - leftBoundary;
28
29 // Update maximum: best from previous segments + current segment
30 maxPrizes = Math.max(maxPrizes, dp[leftBoundary] + currentSegmentPrizes);
31
32 // Update dp[i]: best result using one segment up to position i
33 dp[i] = Math.max(dp[i - 1], currentSegmentPrizes);
34 }
35
36 return maxPrizes;
37 }
38
39 /**
40 * Binary search to find the leftmost index where nums[index] >= target.
41 *
42 * @param nums sorted array to search
43 * @param target the target value to search for
44 * @return the leftmost index where nums[index] >= target
45 */
46 private int binarySearch(int[] nums, int target) {
47 int left = 0;
48 int right = nums.length;
49
50 while (left < right) {
51 int mid = (left + right) >> 1;
52
53 if (nums[mid] >= target) {
54 // Target could be at mid or to the left
55 right = mid;
56 } else {
57 // Target must be to the right
58 left = mid + 1;
59 }
60 }
61
62 return left;
63 }
64}
65
1class Solution {
2public:
3 int maximizeWin(vector<int>& prizePositions, int k) {
4 int n = prizePositions.size();
5
6 // dp[i] stores the maximum number of prizes that can be collected
7 // using one segment ending at or before position i-1
8 vector<int> dp(n + 1);
9
10 int maxPrizes = 0;
11
12 // Iterate through each position as a potential end of the second segment
13 for (int i = 1; i <= n; ++i) {
14 int currentPosition = prizePositions[i - 1];
15
16 // Find the leftmost position that can be included in a segment
17 // ending at current position (segment length <= k)
18 int leftBoundary = lower_bound(prizePositions.begin(),
19 prizePositions.end(),
20 currentPosition - k) - prizePositions.begin();
21
22 // Calculate total prizes:
23 // dp[leftBoundary] = best single segment before leftBoundary
24 // (i - leftBoundary) = prizes in current segment
25 maxPrizes = max(maxPrizes, dp[leftBoundary] + i - leftBoundary);
26
27 // Update dp[i] to store the best single segment ending at or before position i-1
28 dp[i] = max(dp[i - 1], i - leftBoundary);
29 }
30
31 return maxPrizes;
32 }
33};
34
1/**
2 * Finds the maximum number of prizes that can be covered by two segments of length k
3 * @param prizePositions - Array of prize positions in sorted order
4 * @param k - Maximum length of each segment
5 * @returns Maximum number of prizes that can be covered
6 */
7function maximizeWin(prizePositions: number[], k: number): number {
8 const prizeCount: number = prizePositions.length;
9
10 // dp[i] stores the maximum prizes that can be covered by one segment
11 // considering only the first i prizes
12 const maxPrizesUpToIndex: number[] = Array(prizeCount + 1).fill(0);
13
14 let maxTotalPrizes: number = 0;
15
16 /**
17 * Binary search to find the leftmost index where prize position >= targetPosition
18 * @param targetPosition - The position to search for
19 * @returns Index of the first prize at or after targetPosition
20 */
21 const findFirstPrizeAtOrAfter = (targetPosition: number): number => {
22 let leftIndex: number = 0;
23 let rightIndex: number = prizeCount;
24
25 while (leftIndex < rightIndex) {
26 const midIndex: number = (leftIndex + rightIndex) >> 1;
27
28 if (prizePositions[midIndex] >= targetPosition) {
29 rightIndex = midIndex;
30 } else {
31 leftIndex = midIndex + 1;
32 }
33 }
34
35 return leftIndex;
36 };
37
38 // Process each prize position
39 for (let currentIndex: number = 1; currentIndex <= prizeCount; ++currentIndex) {
40 const currentPosition: number = prizePositions[currentIndex - 1];
41
42 // Find the starting index of prizes that can be covered by a segment
43 // ending at current position
44 const segmentStartIndex: number = findFirstPrizeAtOrAfter(currentPosition - k);
45
46 // Calculate prizes covered by current segment
47 const prizesInCurrentSegment: number = currentIndex - segmentStartIndex;
48
49 // Update maximum: best previous segment + current segment
50 maxTotalPrizes = Math.max(
51 maxTotalPrizes,
52 maxPrizesUpToIndex[segmentStartIndex] + prizesInCurrentSegment
53 );
54
55 // Update dp array: maximum prizes covered by one segment up to current index
56 maxPrizesUpToIndex[currentIndex] = Math.max(
57 maxPrizesUpToIndex[currentIndex - 1],
58 prizesInCurrentSegment
59 );
60 }
61
62 return maxTotalPrizes;
63}
64
Time and Space Complexity
Time Complexity: O(n × log n)
The algorithm iterates through all prize positions once with a for loop, which takes O(n)
time. Within each iteration, it performs a binary search using bisect_left(prizePositions, x - k)
to find the leftmost position where we can start a segment that ends at position x
. Since prizePositions
is sorted and has length n
, each binary search operation takes O(log n)
time. Therefore, the overall time complexity is O(n) × O(log n) = O(n × log n)
.
Space Complexity: O(n)
The algorithm uses an array f
of size n + 1
to store the maximum number of prizes that can be collected up to each position. This requires O(n + 1) = O(n)
additional space. The other variables (ans
, i
, j
, x
) use constant space O(1)
. Therefore, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Segment Length
A critical pitfall is confusing the segment length k
with the number of positions it covers. The segment has length k
, meaning it spans from position x
to position x + k
inclusive. This is a continuous range of length k, not k discrete positions.
Incorrect interpretation: Thinking we need to select exactly k prizes. Correct interpretation: We select all prizes within a continuous interval of length k.
Example: If k = 2
and we have prizes at positions [1, 2, 3, 4]
:
- Segment
[1, 3]
has length 2 and covers prizes at positions 1, 2, and 3 - This collects 3 prizes, not 2
2. Off-by-One Error in Binary Search
When finding the leftmost prize position for a segment ending at position x
, developers often make mistakes with the boundary calculation:
Common mistake:
# Wrong: This finds prizes strictly less than x - k j = bisect_left(prizePositions, x - k - 1)
Correct approach:
# Correct: Find the first position >= x - k j = bisect_left(prizePositions, x - k)
The segment [x - k, x]
includes both endpoints, so any prize at position x - k
should be included.
3. Incorrect DP State Update
A subtle error occurs when updating the DP array. The value f[i]
should represent the maximum prizes collectible from the first i
prizes using ONE segment, not the cumulative sum.
Incorrect:
# Wrong: Accumulating all possible prizes f[i] = f[i - 1] + (i - j)
Correct:
# Right: Taking maximum between previous best and current segment
f[i] = max(f[i - 1], i - j)
4. Not Handling Edge Cases
Failing to consider edge cases can lead to incorrect results:
- Empty array: When
prizePositions
is empty - Single prize: When there's only one prize
- Large k value: When
k
is larger than the entire range of positions - All prizes at same position: Multiple prizes at identical positions
Solution: The provided code handles these naturally through the DP approach, but explicit checks can improve clarity:
def maximizeWin(self, prizePositions: List[int], k: int) -> int:
if not prizePositions:
return 0
n = len(prizePositions)
if n <= 2:
return n
# If k covers the entire range, we can collect all prizes with one segment
if k >= prizePositions[-1] - prizePositions[0]:
return n
# Continue with main algorithm...
5. Misunderstanding Segment Overlap
Some developers mistakenly try to ensure segments don't overlap, but the problem explicitly allows overlapping segments. This leads to unnecessarily complex solutions.
Key insight: Overlapping segments are beneficial when prizes are densely packed. The algorithm naturally handles this by considering all possible segment placements without explicitly checking for overlap.
What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
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
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!