Facebook Pixel

3584. Maximum Product of First and Last Elements of a Subsequence

Problem Description

You are given an integer array nums and an integer m. Your task is to find a subsequence of exactly m elements from nums and calculate the product of its first and last elements. You need to return the maximum possible product among all valid subsequences.

A subsequence is a sequence that can be derived from the array by deleting some or no elements without changing the order of the remaining elements. For example, [1, 3, 5] is a subsequence of [1, 2, 3, 4, 5].

The key points to understand:

  • The subsequence must have exactly m elements
  • You need to find the product of the first element and the last element of this subsequence
  • Among all possible subsequences of size m, return the maximum product

For example, if nums = [1, 2, 3, 4] and m = 2, possible subsequences of size 2 are: [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], and [3, 4]. The products of first and last elements would be: 1*2=2, 1*3=3, 1*4=4, 2*3=6, 2*4=8, and 3*4=12. The maximum product is 12.

The solution works by fixing the last element of the subsequence and finding the optimal first element. For each potential last element at position i, the first element must be at position j where j ≤ i - m + 1 (to ensure we can select m-2 elements between them). The algorithm maintains the minimum and maximum values seen so far in the valid range for the first element, as these extremes will give the maximum product when multiplied with the current last element (considering both positive and negative numbers).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that for a subsequence of size m, we only care about the first and last elements since we're computing their product. The middle elements don't affect our answer at all - they just need to exist to make the subsequence valid.

Let's think about this problem step by step. If we fix the last element of our subsequence at position i, what positions can the first element be at? The first element must be at position j where there are at least m-2 positions between j and i to fit the remaining elements. This means j ≤ i - m + 1.

Now, for a fixed last element nums[i], which first element should we choose to maximize the product? Since we're dealing with products, and numbers can be both positive and negative, we need to consider two cases:

  • If nums[i] is positive, we want the largest possible first element
  • If nums[i] is negative, we want the smallest (most negative) first element

This means for each position i that could be the last element, we need to know both the minimum and maximum values among all valid first element positions.

The brilliant part is that as we move i from left to right, the range of valid first elements grows by one position each time. Instead of recalculating the min and max for the entire range each time, we can maintain running values. When we move from i to i+1, we just need to update our min and max by considering the new element that enters the valid range at position i - m + 2.

This sliding window approach allows us to efficiently track the extremes of potential first elements as we iterate through possible last elements, computing the maximum product along the way by trying both nums[i] * min and nums[i] * max at each step.

Learn more about Two Pointers patterns.

Solution Approach

The solution implements the enumeration with prefix extremes approach. We iterate through the array treating each element as a potential last element of our subsequence, while maintaining the minimum and maximum values of all valid first elements.

Here's how the implementation works:

  1. Initialize variables:

    • ans to track the maximum product found (initialized to negative infinity)
    • mx to track the maximum value among valid first elements (initialized to negative infinity)
    • mi to track the minimum value among valid first elements (initialized to positive infinity)
  2. Iterate through potential last elements:

    • Start from index m-1 (the earliest position where we can have a subsequence of size m)
    • For each position i, nums[i] is our current last element
  3. Update the range of valid first elements:

    • When at position i, the valid first element can be at any position from 0 to i - m + 1
    • The element at position i - m + 1 is the newest element entering our valid range
    • Update mi = min(mi, nums[i - m + 1]) to maintain the minimum
    • Update mx = max(mx, nums[i - m + 1]) to maintain the maximum
  4. Calculate products and update answer:

    • With nums[i] as the last element, calculate two products:
      • nums[i] * mi: This gives the maximum product when nums[i] is negative (negative × most negative = positive and large)
      • nums[i] * mx: This gives the maximum product when nums[i] is positive (positive × most positive = positive and large)
    • Update ans = max(ans, nums[i] * mi, nums[i] * mx)
  5. Return the result:

    • After checking all possible last elements, ans contains the maximum product

The algorithm runs in O(n) time complexity where n is the length of the array, as we make a single pass through the array. The space complexity is O(1) as we only use a constant amount of extra variables.

This approach elegantly handles both positive and negative numbers by always considering both extremes (minimum and maximum) of the valid first elements, ensuring we don't miss the optimal product regardless of the sign of the last element.

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 nums = [2, -3, 1, 4, -2] and m = 3.

We need to find a subsequence of exactly 3 elements and maximize the product of its first and last elements.

Initial State:

  • ans = -∞ (maximum product found so far)
  • mx = -∞ (maximum among valid first elements)
  • mi = +∞ (minimum among valid first elements)

Iteration 1: i = 2 (first possible last element for subsequence of size 3)

  • Current last element: nums[2] = 1
  • Valid first element range: positions 0 to 0 (since i - m + 1 = 2 - 3 + 1 = 0)
  • Update extremes with nums[0] = 2:
    • mi = min(+∞, 2) = 2
    • mx = max(-∞, 2) = 2
  • Calculate products:
    • nums[2] * mi = 1 * 2 = 2
    • nums[2] * mx = 1 * 2 = 2
  • Update answer: ans = max(-∞, 2, 2) = 2

Iteration 2: i = 3

  • Current last element: nums[3] = 4
  • Valid first element range: positions 0 to 1 (since i - m + 1 = 3 - 3 + 1 = 1)
  • Update extremes with nums[1] = -3:
    • mi = min(2, -3) = -3
    • mx = max(2, -3) = 2
  • Calculate products:
    • nums[3] * mi = 4 * (-3) = -12
    • nums[3] * mx = 4 * 2 = 8
  • Update answer: ans = max(2, -12, 8) = 8

Iteration 3: i = 4

  • Current last element: nums[4] = -2
  • Valid first element range: positions 0 to 2 (since i - m + 1 = 4 - 3 + 1 = 2)
  • Update extremes with nums[2] = 1:
    • mi = min(-3, 1) = -3
    • mx = max(2, 1) = 2
  • Calculate products:
    • nums[4] * mi = (-2) * (-3) = 6
    • nums[4] * mx = (-2) * 2 = -4
  • Update answer: ans = max(8, 6, -4) = 8

Result: The maximum product is 8, achieved by the subsequence [2, 1, 4] where the first element is 2 and the last element is 4 (2 × 4 = 8).

The key insight demonstrated here is how we maintain running min/max values of valid first elements as we slide through potential last elements. When the last element is negative (like -2), multiplying by the minimum first element (-3) gives us a positive product. When the last element is positive (like 4), multiplying by the maximum first element (2) gives us the best result.

Solution Implementation

1from typing import List
2from math import inf
3
4
5class Solution:
6    def maximumProduct(self, nums: List[int], m: int) -> int:
7        # Initialize variables to track the maximum product and window boundaries
8        max_product = -inf  # Maximum product found so far
9        window_max = -inf    # Maximum value in the current window
10        window_min = inf     # Minimum value in the current window
11      
12        # Iterate through valid positions where a window of size m can end
13        # Window spans from index (i - m + 1) to index i
14        for i in range(m - 1, len(nums)):
15            current_element = nums[i]                # Current element (window end)
16            window_start_element = nums[i - m + 1]   # Element at window start
17          
18            # Update the minimum and maximum values considering the window start element
19            # Note: This tracks min/max of all window start elements seen so far
20            window_min = min(window_min, window_start_element)
21            window_max = max(window_max, window_start_element)
22          
23            # Calculate maximum product using current element with either:
24            # - The minimum seen window start (for negative current_element)
25            # - The maximum seen window start (for positive current_element)
26            max_product = max(max_product, 
27                            current_element * window_min, 
28                            current_element * window_max)
29      
30        return max_product
31
1class Solution {
2    public long maximumProduct(int[] nums, int m) {
3        // Initialize the maximum product result
4        long maxProduct = Long.MIN_VALUE;
5      
6        // Track the maximum and minimum values in the sliding window
7        int windowMax = Integer.MIN_VALUE;
8        int windowMin = Integer.MAX_VALUE;
9      
10        // Iterate through array starting from position (m-1) to ensure window size of m
11        for (int i = m - 1; i < nums.length; i++) {
12            // Current element (rightmost in the window)
13            int currentElement = nums[i];
14          
15            // Leftmost element in the current window of size m
16            int windowStart = nums[i - m + 1];
17          
18            // Update window's minimum and maximum values
19            // Note: This accumulates min/max across all windows seen so far
20            windowMin = Math.min(windowMin, windowStart);
21            windowMax = Math.max(windowMax, windowStart);
22          
23            // Calculate maximum product by multiplying current element with either:
24            // - The minimum value (handles negative * negative case)
25            // - The maximum value (handles positive * positive case)
26            long productWithMin = 1L * currentElement * windowMin;
27            long productWithMax = 1L * currentElement * windowMax;
28          
29            // Update the maximum product found so far
30            maxProduct = Math.max(maxProduct, Math.max(productWithMin, productWithMax));
31        }
32      
33        return maxProduct;
34    }
35}
36
1class Solution {
2public:
3    long long maximumProduct(vector<int>& nums, int m) {
4        // Initialize the maximum product result to the smallest possible value
5        long long maxProduct = LLONG_MIN;
6      
7        // Track the maximum and minimum values in the current window
8        int windowMax = INT_MIN;
9        int windowMin = INT_MAX;
10      
11        // Iterate through all possible windows of size m
12        // Window ends at index i and starts at index (i - m + 1)
13        for (int i = m - 1; i < nums.size(); ++i) {
14            // Current element (right end of the window)
15            int currentElement = nums[i];
16          
17            // Left boundary element of the current window
18            int windowStart = nums[i - m + 1];
19          
20            // Update the minimum and maximum values considering the window start
21            // Note: This maintains running min/max across all windows seen so far
22            windowMin = min(windowMin, windowStart);
23            windowMax = max(windowMax, windowStart);
24          
25            // Calculate the maximum product for current element
26            // Product could be maximized with either the minimum (if negative values exist)
27            // or maximum value in the window
28            long long productWithMin = 1LL * currentElement * windowMin;
29            long long productWithMax = 1LL * currentElement * windowMax;
30          
31            // Update the global maximum product
32            maxProduct = max(maxProduct, max(productWithMin, productWithMax));
33        }
34      
35        return maxProduct;
36    }
37};
38
1/**
2 * Finds the maximum product of two elements where the indices differ by exactly m-1.
3 * For each valid index i, finds the maximum product of nums[i] with any element 
4 * from the window [i-m+1, i-m+1] (which is just nums[i-m+1]).
5 * 
6 * @param nums - The input array of numbers
7 * @param m - The exact distance between the two indices (difference must be m-1)
8 * @returns The maximum product found
9 */
10function maximumProduct(nums: number[], m: number): number {
11    // Initialize the maximum product result to the smallest possible value
12    let maxProduct: number = Number.MIN_SAFE_INTEGER;
13  
14    // Track the maximum value seen in the valid window
15    let maxInWindow: number = Number.MIN_SAFE_INTEGER;
16  
17    // Track the minimum value seen in the valid window
18    let minInWindow: number = Number.MAX_SAFE_INTEGER;
19
20    // Iterate through the array starting from index m-1
21    // This ensures we always have a valid pair at distance m-1
22    for (let i: number = m - 1; i < nums.length; i++) {
23        // Current element to multiply with
24        const currentElement: number = nums[i];
25      
26        // The element at distance m-1 from current position
27        const windowElement: number = nums[i - m + 1];
28      
29        // Update the minimum and maximum values in our window
30        minInWindow = Math.min(minInWindow, windowElement);
31        maxInWindow = Math.max(maxInWindow, windowElement);
32      
33        // Update the maximum product
34        // We check both min and max because negative numbers might give larger product
35        maxProduct = Math.max(
36            maxProduct, 
37            currentElement * minInWindow, 
38            currentElement * maxInWindow
39        );
40    }
41
42    return maxProduct;
43}
44

Time and Space Complexity

The time complexity is O(n), where n is the length of array nums. The algorithm iterates through the array once, starting from index m-1 to the end of the array. In each iteration, it performs constant time operations: calculating minimum and maximum values, and updating the answer. Since we traverse approximately n-m+1 elements with constant work per element, the overall time complexity is linear in terms of n.

The space complexity is O(1). The algorithm only uses a fixed number of variables (ans, mx, mi, x, y, and loop variable i) regardless of the input size. No additional data structures that scale with the input are created, making the space usage constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Misunderstanding the Window Range for Valid First Elements

The Problem: A common mistake is thinking that window_min and window_max only track the single element at position i - m + 1 for each iteration. This misunderstanding leads to incorrect solutions where developers reset or only consider the current window start element.

Why This Happens: The variable naming window_start_element and the comment about "window" can be misleading. Students often interpret this as a sliding window that only considers elements within the current window of size m.

The Correct Understanding:

  • window_min and window_max accumulate ALL valid first elements seen so far
  • When at position i, any element from index 0 to i - m + 1 can be the first element
  • We're building up a range of all possible first elements, not just looking at the current window

Example to Illustrate:

nums = [3, -2, 1, 4], m = 3

At i = 2 (nums[2] = 1):
  - Valid first elements: nums[0] = 3 (only one so far)
  - window_min = 3, window_max = 3

At i = 3 (nums[3] = 4):
  - Valid first elements: nums[0] = 3, nums[1] = -2
  - window_min = min(3, -2) = -2
  - window_max = max(3, -2) = 3

Pitfall 2: Incorrect Initialization or Update Order

The Problem: Developers might update window_min and window_max AFTER calculating the product, or initialize them with the wrong values.

Incorrect Implementation:

# WRONG: Calculating product before updating min/max
for i in range(m - 1, len(nums)):
    max_product = max(max_product, 
                    nums[i] * window_min, 
                    nums[i] * window_max)
    # Updates happen after calculation - misses valid first elements!
    window_min = min(window_min, nums[i - m + 1])
    window_max = max(window_max, nums[i - m + 1])

Correct Implementation:

# CORRECT: Update min/max first, then calculate product
for i in range(m - 1, len(nums)):
    # Update the range of valid first elements
    window_min = min(window_min, nums[i - m + 1])
    window_max = max(window_max, nums[i - m + 1])
    # Now calculate using the updated values
    max_product = max(max_product, 
                    nums[i] * window_min, 
                    nums[i] * window_max)

Solution to Avoid These Pitfalls:

Use clearer variable names and add explicit comments about the accumulative nature:

class Solution:
    def maximumProduct(self, nums: List[int], m: int) -> int:
        max_product = -inf
        # These track ALL valid first elements seen so far, not just current window
        all_valid_first_min = inf
        all_valid_first_max = -inf
      
        for i in range(m - 1, len(nums)):
            last_element = nums[i]
            # The newest element that can be a valid first element
            newest_valid_first = nums[i - m + 1]
          
            # Accumulate min/max across ALL valid first elements
            all_valid_first_min = min(all_valid_first_min, newest_valid_first)
            all_valid_first_max = max(all_valid_first_max, newest_valid_first)
          
            # Calculate product with accumulated extremes
            max_product = max(max_product,
                            last_element * all_valid_first_min,
                            last_element * all_valid_first_max)
      
        return max_product
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

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

Load More