Maximum Product of First and Last Elements of a Subsequence
MEDIUMDescription
You are given an integer array nums and an integer m.
Return the maximum product of the first and last elements of any subsequence of nums of size m.
Example 1:
Input: nums = [-1,-9,2,3,-2,-3,1], m = 1
Output: 81
Explanation:
The subsequence [-9] has the largest product of the first and last elements: -9 * -9 = 81. Therefore, the answer is 81.
Example 2:
Input: nums = [1,3,-5,5,6,-4], m = 3
Output: 20
Explanation:
The subsequence [-5, 6, -4] has the largest product of the first and last elements.
Example 3:
Input: nums = [2,-1,2,-6,5,2,-5,7], m = 2
Output: 35
Explanation:
The subsequence [5, 7] has the largest product of the first and last elements.
Constraints:
1 <= nums.length <= 105-105 <= nums[i] <= 1051 <= m <= nums.length
Approaches
Checkout 3 different approaches to solve Maximum Product of First and Last Elements of a Subsequence. Click on different approaches to view the approach and algorithm in detail.
Optimal Single-Pass Approach
The most optimal solution achieves linear time complexity without using any extra space (O(1) space). We can combine the precomputation and the main calculation into a single pass. We use a two-pointer-like approach. One pointer j iterates from m-1 to the end of the array, representing the last element of the subsequence. Another pointer i_ptr tracks the end of the prefix from which the first element can be chosen. As j moves forward, i_ptr also moves forward, and we maintain the running minimum and maximum of the prefix ending at i_ptr. This way, we always have the necessary information to calculate the best product for each j without storing entire prefix arrays.
Algorithm
This approach builds upon the logic of the prefix array method but eliminates the need for extra space by computing the prefix information on-the-fly.
- Initialize
maxProductto a very small value. - Initialize
prefixMaxandprefixMintonums[0]. These will track the running min/max of the prefix relevant to the first elementnums[i]. - Initialize a pointer,
i_ptr, to0. This pointer marks the end of the prefix for whichprefixMaxandprefixMinhave been calculated. - Iterate with a pointer
jfromm-1ton-1. Thisjrepresents the index of the last element. - For each
j, the maximum possible index for the first elementiisi_limit = j - m + 1. - We need the min/max of the prefix
nums[0...i_limit]. Since ouri_ptrmight be behindi_limit, we advancei_ptrup toi_limit, updatingprefixMaxandprefixMinwith each new element encountered. - Once
i_ptrequalsi_limit,prefixMaxandprefixMinhold the required values. - Calculate the candidate product using
nums[j]with bothprefixMaxandprefixMin. - Update the global
maxProductwith the larger of these candidates. - After the loop over
jfinishes,maxProductholds the result.
This approach refines the prefix array method to use constant extra space. Instead of pre-calculating and storing all prefix minimums and maximums in an array, we can calculate them as needed during a single pass.
We iterate through the possible last element indices j from m-1 to n-1. For each j, the first element nums[i] can be chosen from the prefix nums[0...j-m+1]. We can maintain the minimum and maximum of this prefix in two variables. As j increments, the prefix nums[0...j-m+1] grows by one element. We can update our running prefix min/max in O(1) amortized time. This is a form of a sliding window or two-pointer technique where one pointer j scans the array, and another pointer i_ptr defines the boundary of the prefix being considered.
This single-pass algorithm is optimal in both time and space.
class Solution {
public long maximumProduct(int[] nums, int m) {
int n = nums.length;
// This logic correctly handles m=1, as j-i >= 0 means i<=j, and
// max_{i<=j}(nums[i]*nums[j]) is equivalent to max_k(nums[k]*nums[k]).
if (n < m) {
return 0; // Or throw an exception for invalid input
}
long maxProduct = Long.MIN_VALUE;
long prefixMax = nums[0];
long prefixMin = nums[0];
int i_ptr = 0;
// Iterate j as the index of the last element
for (int j = m - 1; j < n; j++) {
// The first element's index `i` can be at most `j - m + 1`
int i_limit = j - m + 1;
// Update prefix min/max to cover the range up to i_limit
// This inner loop runs disjointly over the course of the outer loop
while (i_ptr < i_limit) {
i_ptr++;
prefixMax = Math.max(prefixMax, nums[i_ptr]);
prefixMin = Math.min(prefixMin, nums[i_ptr]);
}
// Now prefixMax/prefixMin are the min/max of nums[0...i_limit]
long currentVal = nums[j];
long candidate1 = currentVal * prefixMax;
long candidate2 = currentVal * prefixMin;
maxProduct = Math.max(maxProduct, Math.max(candidate1, candidate2));
}
return maxProduct;
}
}
Complexity Analysis
Pros and Cons
- Optimal time complexity of O(N).
- Optimal space complexity of O(1).
- Efficiently solves the problem in a single pass.
- The logic is slightly more complex to implement correctly compared to the O(N) space solution due to the coupled movement of the two pointers.
Video Solution
Watch the video walkthrough for Maximum Product of First and Last Elements of a Subsequence
Similar Questions
5 related questions you might find useful
Patterns:
Data Structures:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.