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.
This approach directly simulates the process described in the problem. We consider every possible scenario: either not removing any element or removing exactly one element from the array. For each of these n+1 scenarios, where n is the length of the array, we form the resulting subarray and calculate its factor score. The maximum score found across all scenarios is the answer.
Algorithm
- Initialize a variable
maxScoreto 0, using alongtype to prevent overflow during intermediate calculations. - Define helper functions
gcd(a, b)andlcm(a, b). Thelcmfunction should handle potential overflows by usinglongarithmetic. - First, consider the case where no element is removed. Create a list from the input
numsarray. - Calculate the GCD and LCM of all elements in this list. The factor score is their product. Update
maxScorewith this score. - Iterate through the
numsarray with an indexifrom0ton-1. - In each iteration, create a new temporary list containing all elements of
numsexceptnums[i]. - If the temporary list is not empty, calculate its GCD and LCM, and then its factor score.
- Update
maxScore = Math.max(maxScore, newScore). - After the loop finishes,
maxScoreholds the maximum possible factor score. - Cast the final
maxScoretointand return it.
Walkthrough
The core of this method is to iterate through all n+1 possibilities.
First, we calculate the factor score for the original array without any removals. This serves as our initial maximum score.
Then, we loop from i = 0 to n-1. In each iteration i, we simulate the removal of the element nums[i]. To do this, we construct a temporary subarray that contains all elements of nums except for nums[i]. For this temporary subarray, we calculate its Greatest Common Divisor (GCD) and Least Common Multiple (LCM).
The GCD can be found by iteratively applying the Euclidean algorithm to all elements of the subarray. The LCM can be found similarly, using the formula lcm(a, b) = (a * b) / gcd(a, b). It's important to use 64-bit integers (long in Java) for LCM and score calculations to prevent overflow, as intermediate values can become very large even if the final answer fits in a 32-bit integer.
The factor score for this subarray is GCD * LCM. We compare this score with our current maximum score and update it if the new score is greater. After checking all n possible removals, the final maximum score is our result.
import java.util.ArrayList;import java.util.List; 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 } private long calculateScore(List<Integer> list) { if (list.isEmpty()) { return 0; } if (list.size() == 1) { long val = list.get(0); return val * val; } long currentGcd = list.get(0); long currentLcm = list.get(0); for (int i = 1; i < list.size(); i++) { currentGcd = gcd(currentGcd, list.get(i)); currentLcm = lcm(currentLcm, list.get(i)); } return currentGcd * currentLcm; } public int maxFactorScore(int[] nums) { long maxScore = 0; int n = nums.length; // Case 1: No element removed List<Integer> fullList = new ArrayList<>(); for (int num : nums) { fullList.add(num); } maxScore = calculateScore(fullList); // Case 2: One element removed if (n > 1) { for (int i = 0; i < n; i++) { List<Integer> subList = new ArrayList<>(); for (int j = 0; j < n; j++) { if (i != j) { subList.add(nums[j]); } } maxScore = Math.max(maxScore, calculateScore(subList)); } } return (int) maxScore; }}Complexity
Time
O(N^2 * log(M)), where N is the number of elements in `nums` and M is the maximum value in `nums`. For each of the `N+1` subarrays, we iterate through its elements (up to `N`) to compute GCD and LCM. Each GCD/LCM operation takes `O(log(M))` time.
Space
O(N), where N is the number of elements in `nums`. This space is used to store the temporary subarray in each iteration of the main loop.
Trade-offs
Pros
Simple to understand and implement.
Directly follows the problem definition, making it less prone to logical errors.
Cons
Inefficient due to redundant calculations. For each subarray, GCD and LCM are computed from scratch, leading to a quadratic time complexity.
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; }}class Solution: def maxScore(self, nums: List[int]) -> int: n = len(nums) suf_gcd = [0] * (n + 1) suf_lcm = [0] * n + [1] for i in range(n - 1, - 1, - 1): suf_gcd[i] = gcd(suf_gcd[i + 1], nums[i]) suf_lcm[i] = lcm(suf_lcm[i + 1], nums[i]) ans = suf_gcd[0] * suf_lcm[0] pre_gcd, pre_lcm = 0, 1 for i, x in enumerate(nums): ans = max(ans, gcd(pre_gcd, suf_gcd[i + 1]) * lcm(pre_lcm, suf_lcm[i + 1])) pre_gcd = gcd(pre_gcd, x) pre_lcm = lcm(pre_lcm, x) return ansclass Solution {public: long long maxScore(vector<int> &nums) { int n = nums.size(); vector<long long> sufGcd(n + 1, 0); vector<long long> sufLcm(n + 1, 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 long ans = sufGcd[0] * sufLcm[0]; long long preGcd = 0, preLcm = 1; for (int i = 0; i < n; ++i) { ans = max(ans, gcd(preGcd, sufGcd[i + 1]) * lcm(preLcm, sufLcm[i + 1])); preGcd = gcd(preGcd, nums[i]); preLcm = lcm(preLcm, nums[i]); } return ans; }};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.