Find the Maximum Factor Score of Array
MedPrompt
You are given an integer array nums.
The factor score of an array is defined as the product of the LCM and GCD of all elements of that array.
Return the maximum factor score of nums after removing at most one element from it.
Note that both the LCM and GCD of a single number are the number itself, and the factor score of an empty array is 0.
Example 1:
Input: nums = [2,4,8,16]
Output: 64
Explanation:
On removing 2, the GCD of the rest of the elements is 4 while the LCM is 16, which gives a maximum factor score of 4 * 16 = 64.
Example 2:
Input: nums = [1,2,3,4,5]
Output: 60
Explanation:
The maximum factor score of 60 can be obtained without removing any elements.
Example 3:
Input: nums = [3]
Output: 9
Constraints:
1 <= nums.length <= 1001 <= nums[i] <= 30
Approaches
2 approaches with complexity analysis and trade-offs.
This approach improves upon the brute-force method by avoiding redundant computations. The key idea is to precompute the GCD and LCM of all prefixes and suffixes of the array. With these precomputed values, we can find the GCD and LCM of any subarray (formed by removing one element) in constant time (plus the time for a single GCD/LCM operation).
Algorithm
- Handle the edge case where
n=1. The score isnums[0] * nums[0]. - Create four arrays of size
nto store prefix/suffix GCDs and LCMs:prefixGcd,suffixGcd,prefixLcm,suffixLcm. Uselongtype for LCM arrays to prevent overflow. - Calculate all prefix GCDs and LCMs.
prefixGcd[i]is the GCD ofnums[0...i].prefixLcm[i]is the LCM ofnums[0...i]. This takes a single pass from left to right. - Calculate all suffix GCDs and LCMs.
suffixGcd[i]is the GCD ofnums[i...n-1].suffixLcm[i]is the LCM ofnums[i...n-1]. This takes a single pass from right to left. - Calculate the factor score for the original array (no removals) using
prefixGcd[n-1]andprefixLcm[n-1]. InitializemaxScorewith this value. - Iterate with an index
ifrom0ton-1to simulate removingnums[i]. - For each
i, determine the GCD and LCM of the remaining elements using the precomputed arrays.- If
iis the first element, the new GCD/LCM aresuffixGcd[1]andsuffixLcm[1]. - If
iis the last element, they areprefixGcd[n-2]andprefixLcm[n-2]. - Otherwise, they are
gcd(prefixGcd[i-1], suffixGcd[i+1])andlcm(prefixLcm[i-1], suffixLcm[i+1]).
- If
- Calculate the factor score for this configuration and update
maxScoreif it's larger. - After the loop, cast the final
maxScoretointand return it.
Walkthrough
When an element nums[i] is removed, the remaining elements form two contiguous blocks: nums[0...i-1] and nums[i+1...n-1]. The GCD of the new subarray is GCD(GCD of nums[0...i-1], GCD of nums[i+1...n-1]). A similar property holds for the LCM.
This observation allows us to use precomputation. We create four arrays:
prefixGcd[i]: GCD ofnums[0]throughnums[i].suffixGcd[i]: GCD ofnums[i]throughnums[n-1].prefixLcm[i]: LCM ofnums[0]throughnums[i].suffixLcm[i]: LCM ofnums[i]throughnums[n-1].
These arrays can be populated in O(N * log(M)) time. For example, prefixGcd[i] = gcd(prefixGcd[i-1], nums[i]).
After precomputation, we first calculate the score for the full array (suffixGcd[0] * suffixLcm[0]). Then, we iterate from i = 0 to n-1 to consider removing nums[i]. For each i, the GCD and LCM of the remaining elements are found by combining the corresponding prefix and suffix values in O(log(M)) time. This significantly speeds up the process compared to re-calculating from scratch each time.
class Solution { private long gcd(long a, long b) { while (b != 0) { long temp = b; b = a % b; a = temp; } return a; } private long lcm(long a, long b) { if (a == 0 || b == 0) return 0; if (a == 1) return b; if (b == 1) return a; return (a / gcd(a, b)) * b; // Divide first to prevent overflow } public int maxFactorScore(int[] nums) { int n = nums.length; if (n == 1) { long val = nums[0]; return (int) (val * val); } long[] prefixGcd = new long[n]; long[] suffixGcd = new long[n]; long[] prefixLcm = new long[n]; long[] suffixLcm = new long[n]; // Populate prefix arrays prefixGcd[0] = nums[0]; prefixLcm[0] = nums[0]; for (int i = 1; i < n; i++) { prefixGcd[i] = gcd(prefixGcd[i - 1], nums[i]); prefixLcm[i] = lcm(prefixLcm[i - 1], nums[i]); } // Populate suffix arrays suffixGcd[n - 1] = nums[n - 1]; suffixLcm[n - 1] = nums[n - 1]; for (int i = n - 2; i >= 0; i--) { suffixGcd[i] = gcd(suffixGcd[i + 1], nums[i]); suffixLcm[i] = lcm(suffixLcm[i + 1], nums[i]); } // Case 1: No removal long maxScore = prefixGcd[n - 1] * prefixLcm[n - 1]; // Case 2: One element removed for (int i = 0; i < n; i++) { long currentGcd, currentLcm; if (i == 0) { // Remove first element currentGcd = suffixGcd[1]; currentLcm = suffixLcm[1]; } else if (i == n - 1) { // Remove last element currentGcd = prefixGcd[n - 2]; currentLcm = prefixLcm[n - 2]; } else { // Remove middle element currentGcd = gcd(prefixGcd[i - 1], suffixGcd[i + 1]); currentLcm = lcm(prefixLcm[i - 1], suffixLcm[i + 1]); } maxScore = Math.max(maxScore, currentGcd * currentLcm); } return (int) maxScore; }}Complexity
Time
O(N * log(M)), where N is the number of elements and M is the maximum value. Populating the prefix/suffix arrays takes `O(N * log(M))`. The final loop to check all removal scenarios also takes `O(N * log(M))` because each `lcm`/`gcd` call is `O(log(M))`. This is a major improvement over the brute-force approach.
Space
O(N), where N is the number of elements in `nums`. This space is used for the four prefix and suffix arrays.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Reduces the overall time complexity from quadratic to linearithmic.
Cons
Requires extra space proportional to the input size to store the prefix and suffix arrays.
The implementation is slightly more complex than the brute-force approach.
Solutions
Solution
class Solution {public long maxScore(int[] nums) { int n = nums.length; long[] sufGcd = new long[n + 1]; long[] sufLcm = new long[n + 1]; sufLcm[n] = 1; for (int i = n - 1; i >= 0; --i) { sufGcd[i] = gcd(sufGcd[i + 1], nums[i]); sufLcm[i] = lcm(sufLcm[i + 1], nums[i]); } long ans = sufGcd[0] * sufLcm[0]; long preGcd = 0, preLcm = 1; for (int i = 0; i < n; ++i) { ans = Math.max(ans, gcd(preGcd, sufGcd[i + 1]) * lcm(preLcm, sufLcm[i + 1])); preGcd = gcd(preGcd, nums[i]); preLcm = lcm(preLcm, nums[i]); } return ans; }private long gcd(long a, long b) { return b == 0 ? a : gcd(b, a % b); }private long lcm(long a, long b) { return a / gcd(a, b) * b; }}Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.