Facebook Pixel

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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Determine how many prizes the second segment collects
  2. 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 where prizePositions[j] >= prizePositions[i] - k. This tells us the starting point of a segment of length k that ends at or after position i.
  • The second segment collects i - j prizes (from index j to i-1)
  • The first segment should be chosen optimally from prizes before index j, which is exactly f[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 size n + 1 where f[i] stores the maximum number of prizes that can be collected using one segment from the first i 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 us f[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 Evaluator

Example 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More