3334. Find the Maximum Factor Score of Array
Problem Description
You are given an integer array nums
.
The factor score of an array is calculated as the product of two values:
- The LCM (Least Common Multiple) of all elements in the array
- The GCD (Greatest Common Divisor) of all elements in the array
Your task is to find the maximum possible factor score after removing at most one element from the array. This means you can either:
- Keep all elements (remove nothing)
- Remove exactly one element
Important notes:
- For a single-element array, both LCM and GCD equal the element itself
- For an empty array, the factor score is 0
- The factor score =
LCM(array) × GCD(array)
For example, if nums = [6, 12, 18]
:
- Without removing any element:
GCD(6,12,18) = 6
,LCM(6,12,18) = 36
, factor score =6 × 36 = 216
- After removing 6:
GCD(12,18) = 6
,LCM(12,18) = 36
, factor score =6 × 36 = 216
- After removing 12:
GCD(6,18) = 6
,LCM(6,18) = 18
, factor score =6 × 18 = 108
- After removing 18:
GCD(6,12) = 6
,LCM(6,12) = 12
, factor score =6 × 12 = 72
The maximum factor score would be 216.
Intuition
To find the maximum factor score, we need to consider all possible scenarios: keeping all elements or removing exactly one element. The brute force approach would be to try removing each element one by one and calculate the factor score for each case, but this would involve recalculating GCD and LCM from scratch each time, which is inefficient.
The key insight is that when we remove element at index i
, the resulting array is essentially the combination of two parts:
- Elements before index
i
(prefix:nums[0...i-1]
) - Elements after index
i
(suffix:nums[i+1...n-1]
)
The GCD and LCM of the resulting array would be:
GCD = gcd(GCD_of_prefix, GCD_of_suffix)
LCM = lcm(LCM_of_prefix, LCM_of_suffix)
This observation leads us to a preprocessing strategy. If we precompute the GCD and LCM values for all suffixes, we can efficiently calculate the factor score when any element is removed.
As we iterate through the array from left to right:
- We maintain running prefix GCD and LCM values
- For each position
i
, we can instantly know what the GCD and LCM would be if we removed elementi
by combining:- The prefix values (elements before
i
) - The precomputed suffix values (elements after
i
)
- The prefix values (elements before
This way, we only need one pass to precompute suffix values and another pass to check all possibilities, reducing the time complexity significantly. The solution also handles the case where we don't remove any element (which is just the GCD and LCM of the entire array).
Learn more about Math patterns.
Solution Approach
The implementation uses prefix and suffix arrays to efficiently calculate the factor score for all possible scenarios.
Step 1: Initialize Suffix Arrays
We create two arrays to store suffix GCD and LCM values:
suf_gcd[i]
stores the GCD of all elements from indexi
to the endsuf_lcm[i]
stores the LCM of all elements from indexi
to the end
suf_gcd = [0] * (n + 1) # Extra element for boundary condition suf_lcm = [0] * n + [1] # Initialize last element as 1 for LCM
Step 2: Build Suffix Arrays (Right to Left)
We iterate from the last element to the first, building up the suffix values:
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])
For each position i
, we combine the current element nums[i]
with the suffix starting at i+1
.
Step 3: Calculate Initial Answer (No Removal)
First, we consider keeping all elements:
ans = suf_gcd[0] * suf_lcm[0]
This gives us the factor score of the entire array.
Step 4: Check All Removal Scenarios
We maintain prefix GCD and LCM as we iterate through the array:
pre_gcd, pre_lcm = 0, 1 # Identity values for GCD and LCM
for i, x in enumerate(nums):
# Calculate score if we remove element at index i
ans = max(ans, gcd(pre_gcd, suf_gcd[i + 1]) * lcm(pre_lcm, suf_lcm[i + 1]))
# Update prefix values for next iteration
pre_gcd = gcd(pre_gcd, x)
pre_lcm = lcm(pre_lcm, x)
When removing element at index i
:
- The remaining array's GCD =
gcd(prefix_gcd, suffix_gcd)
- The remaining array's LCM =
lcm(prefix_lcm, suffix_lcm)
- Factor score =
GCD × LCM
Key Implementation Details:
-
Identity Values: We use
0
for GCD and1
for LCM as identity values. This ensuresgcd(0, x) = x
andlcm(1, x) = x
. -
Boundary Handling: The suffix arrays have an extra element to handle the case when we're at the last position (empty suffix).
-
Maximum Tracking: We continuously update
ans
with the maximum factor score found.
The algorithm runs in O(n × log(max_value))
time, where the logarithmic factor comes from the GCD/LCM calculations, and uses O(n)
extra space for the suffix arrays.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [4, 6, 12]
.
Step 1: Build Suffix Arrays
We initialize suffix arrays with extra space:
suf_gcd = [0, 0, 0, 0]
(4 elements)suf_lcm = [0, 0, 0, 1]
(last element is 1)
Building from right to left:
- i = 2:
suf_gcd[2] = gcd(0, 12) = 12
,suf_lcm[2] = lcm(1, 12) = 12
- i = 1:
suf_gcd[1] = gcd(12, 6) = 6
,suf_lcm[1] = lcm(12, 6) = 12
- i = 0:
suf_gcd[0] = gcd(6, 4) = 2
,suf_lcm[0] = lcm(12, 4) = 12
Final suffix arrays:
suf_gcd = [2, 6, 12, 0]
suf_lcm = [12, 12, 12, 1]
Step 2: Calculate Initial Answer (Keep All Elements)
Factor score with all elements = suf_gcd[0] × suf_lcm[0] = 2 × 12 = 24
Step 3: Check Each Removal Scenario
Initialize prefix values: pre_gcd = 0
, pre_lcm = 1
Iteration 1 (i = 0, removing 4):
- Remaining elements: [6, 12]
- GCD =
gcd(pre_gcd=0, suf_gcd[1]=6) = 6
- LCM =
lcm(pre_lcm=1, suf_lcm[1]=12) = 12
- Factor score =
6 × 12 = 72
- Update:
ans = max(24, 72) = 72
- Update prefix:
pre_gcd = gcd(0, 4) = 4
,pre_lcm = lcm(1, 4) = 4
Iteration 2 (i = 1, removing 6):
- Remaining elements: [4, 12]
- GCD =
gcd(pre_gcd=4, suf_gcd[2]=12) = 4
- LCM =
lcm(pre_lcm=4, suf_lcm[2]=12) = 12
- Factor score =
4 × 12 = 48
- Update:
ans = max(72, 48) = 72
- Update prefix:
pre_gcd = gcd(4, 6) = 2
,pre_lcm = lcm(4, 6) = 12
Iteration 3 (i = 2, removing 12):
- Remaining elements: [4, 6]
- GCD =
gcd(pre_gcd=2, suf_gcd[3]=0) = 2
- LCM =
lcm(pre_lcm=12, suf_lcm[3]=1) = 12
- Factor score =
2 × 12 = 24
- Update:
ans = max(72, 24) = 72
Final Answer: 72 (achieved by removing element 4)
The solution efficiently considers all scenarios:
- Keep all: factor score = 24
- Remove 4: factor score = 72 ✓ (maximum)
- Remove 6: factor score = 48
- Remove 12: factor score = 24
Solution Implementation
1class Solution:
2 def maxScore(self, nums: List[int]) -> int:
3 from math import gcd
4
5 def lcm(a: int, b: int) -> int:
6 """Calculate the least common multiple of two numbers."""
7 if a == 0 or b == 0:
8 return 0
9 return abs(a * b) // gcd(a, b)
10
11 n = len(nums)
12
13 # Build suffix arrays for GCD and LCM values from right to left
14 suffix_gcd = [0] * (n + 1) # suffix_gcd[i] = GCD of nums[i:]
15 suffix_lcm = [1] * (n + 1) # suffix_lcm[i] = LCM of nums[i:]
16
17 # Calculate suffix GCD and LCM for each position
18 for i in range(n - 1, -1, -1):
19 suffix_gcd[i] = gcd(suffix_gcd[i + 1], nums[i])
20 suffix_lcm[i] = lcm(suffix_lcm[i + 1], nums[i])
21
22 # Initialize answer with score using all elements
23 max_score = suffix_gcd[0] * suffix_lcm[0]
24
25 # Track prefix GCD and LCM as we iterate through the array
26 prefix_gcd = 0
27 prefix_lcm = 1
28
29 # Try removing each element and calculate the score
30 for i in range(n):
31 # Score when removing nums[i]: combine prefix (before i) and suffix (after i)
32 score_without_current = gcd(prefix_gcd, suffix_gcd[i + 1]) * lcm(prefix_lcm, suffix_lcm[i + 1])
33 max_score = max(max_score, score_without_current)
34
35 # Update prefix values to include current element
36 prefix_gcd = gcd(prefix_gcd, nums[i])
37 prefix_lcm = lcm(prefix_lcm, nums[i])
38
39 return max_score
40
1class Solution {
2 /**
3 * Calculates the maximum score by finding the optimal product of GCD and LCM
4 * after potentially removing one element from the array.
5 * Score = GCD of remaining elements * LCM of remaining elements
6 *
7 * @param nums the input array of integers
8 * @return the maximum possible score
9 */
10 public long maxScore(int[] nums) {
11 int n = nums.length;
12
13 // Arrays to store suffix GCD and LCM values
14 // suffixGcd[i] = GCD of all elements from index i to n-1
15 // suffixLcm[i] = LCM of all elements from index i to n-1
16 long[] suffixGcd = new long[n + 1];
17 long[] suffixLcm = new long[n + 1];
18
19 // Initialize base case: empty suffix has LCM of 1 (identity for LCM)
20 suffixLcm[n] = 1;
21
22 // Build suffix arrays from right to left
23 for (int i = n - 1; i >= 0; i--) {
24 suffixGcd[i] = gcd(suffixGcd[i + 1], nums[i]);
25 suffixLcm[i] = lcm(suffixLcm[i + 1], nums[i]);
26 }
27
28 // Calculate score using all elements (no removal)
29 long maxScore = suffixGcd[0] * suffixLcm[0];
30
31 // Track prefix GCD and LCM as we iterate
32 long prefixGcd = 0; // Identity for GCD is 0
33 long prefixLcm = 1; // Identity for LCM is 1
34
35 // Try removing each element and calculate the score
36 for (int i = 0; i < n; i++) {
37 // Score when removing element at index i:
38 // GCD of (prefix before i) and (suffix after i) * LCM of (prefix before i) and (suffix after i)
39 long currentScore = gcd(prefixGcd, suffixGcd[i + 1]) * lcm(prefixLcm, suffixLcm[i + 1]);
40 maxScore = Math.max(maxScore, currentScore);
41
42 // Update prefix values to include current element
43 prefixGcd = gcd(prefixGcd, nums[i]);
44 prefixLcm = lcm(prefixLcm, nums[i]);
45 }
46
47 return maxScore;
48 }
49
50 /**
51 * Calculates the Greatest Common Divisor (GCD) using Euclidean algorithm
52 *
53 * @param a first number
54 * @param b second number
55 * @return GCD of a and b
56 */
57 private long gcd(long a, long b) {
58 return b == 0 ? a : gcd(b, a % b);
59 }
60
61 /**
62 * Calculates the Least Common Multiple (LCM) using the formula:
63 * LCM(a, b) = (a * b) / GCD(a, b)
64 * Division is performed before multiplication to avoid overflow
65 *
66 * @param a first number
67 * @param b second number
68 * @return LCM of a and b
69 */
70 private long lcm(long a, long b) {
71 return a / gcd(a, b) * b;
72 }
73}
74
1class Solution {
2public:
3 long long maxScore(vector<int>& nums) {
4 int n = nums.size();
5
6 // suffix[i] stores the GCD of all elements from index i to n-1
7 vector<long long> suffixGcd(n + 1, 0);
8 // suffix[i] stores the LCM of all elements from index i to n-1
9 vector<long long> suffixLcm(n + 1, 1);
10
11 // Build suffix arrays from right to left
12 for (int i = n - 1; i >= 0; --i) {
13 suffixGcd[i] = gcd(suffixGcd[i + 1], nums[i]);
14 suffixLcm[i] = lcm(suffixLcm[i + 1], nums[i]);
15 }
16
17 // Initialize answer with score when no element is removed (all elements included)
18 long long maxResult = suffixGcd[0] * suffixLcm[0];
19
20 // Track prefix GCD and LCM as we iterate
21 long long prefixGcd = 0;
22 long long prefixLcm = 1;
23
24 // Try removing each element and calculate the score
25 for (int i = 0; i < n; ++i) {
26 // Score when removing element at index i:
27 // GCD of (prefix[0...i-1] and suffix[i+1...n-1]) * LCM of (prefix[0...i-1] and suffix[i+1...n-1])
28 long long currentScore = gcd(prefixGcd, suffixGcd[i + 1]) * lcm(prefixLcm, suffixLcm[i + 1]);
29 maxResult = max(maxResult, currentScore);
30
31 // Update prefix values for next iteration
32 prefixGcd = gcd(prefixGcd, nums[i]);
33 prefixLcm = lcm(prefixLcm, nums[i]);
34 }
35
36 return maxResult;
37 }
38};
39
1/**
2 * Calculates the maximum score by finding the optimal GCD * LCM value
3 * when potentially removing one element from the array
4 * @param nums - The input array of numbers
5 * @returns The maximum score (GCD * LCM)
6 */
7function maxScore(nums: number[]): number {
8 const n: number = nums.length;
9
10 // Suffix arrays to store GCD and LCM values from right to left
11 const suffixGcd: number[] = Array(n + 1).fill(0);
12 const suffixLcm: number[] = Array(n + 1).fill(1);
13
14 // Build suffix GCD and LCM arrays
15 // suffixGcd[i] = GCD of all elements from index i to end
16 // suffixLcm[i] = LCM of all elements from index i to end
17 for (let i: number = n - 1; i >= 0; i--) {
18 suffixGcd[i] = gcd(suffixGcd[i + 1], nums[i]);
19 suffixLcm[i] = lcm(suffixLcm[i + 1], nums[i]);
20 }
21
22 // Initialize answer with the score using all elements
23 let maxResult: number = suffixGcd[0] * suffixLcm[0];
24
25 // Prefix values to track GCD and LCM from left
26 let prefixGcd: number = 0;
27 let prefixLcm: number = 1;
28
29 // Try removing each element and calculate the score
30 for (let i: number = 0; i < n; i++) {
31 // Calculate score when removing element at index i
32 // by combining prefix (before i) and suffix (after i)
33 const currentScore: number = gcd(prefixGcd, suffixGcd[i + 1]) * lcm(prefixLcm, suffixLcm[i + 1]);
34 maxResult = Math.max(maxResult, currentScore);
35
36 // Update prefix values for next iteration
37 prefixGcd = gcd(prefixGcd, nums[i]);
38 prefixLcm = lcm(prefixLcm, nums[i]);
39 }
40
41 return maxResult;
42}
43
44/**
45 * Calculates the Greatest Common Divisor (GCD) of two numbers
46 * using Euclidean algorithm
47 * @param a - First number
48 * @param b - Second number
49 * @returns The GCD of a and b
50 */
51function gcd(a: number, b: number): number {
52 return b === 0 ? a : gcd(b, a % b);
53}
54
55/**
56 * Calculates the Least Common Multiple (LCM) of two numbers
57 * using the formula: LCM(a,b) = (a * b) / GCD(a,b)
58 * @param a - First number
59 * @param b - Second number
60 * @returns The LCM of a and b
61 */
62function lcm(a: number, b: number): number {
63 return (a / gcd(a, b)) * b;
64}
65
Time and Space Complexity
Time Complexity: O(n * log(max(nums)))
The algorithm consists of three main parts:
-
Preprocessing suffix arrays (
O(n * log(max(nums)))
):- The loop runs
n
times - Each iteration computes
gcd(suf_gcd[i + 1], nums[i])
andlcm(suf_lcm[i + 1], nums[i])
- Both GCD and LCM operations have time complexity
O(log(max(nums)))
using Euclidean algorithm - Total:
O(n * log(max(nums)))
- The loop runs
-
Main loop to find maximum score (
O(n * log(max(nums)))
):- The loop runs
n
times - Each iteration computes:
gcd(pre_gcd, suf_gcd[i + 1])
-O(log(max(nums)))
lcm(pre_lcm, suf_lcm[i + 1])
-O(log(max(nums)))
gcd(pre_gcd, x)
-O(log(max(nums)))
lcm(pre_lcm, x)
-O(log(max(nums)))
- Total:
O(n * log(max(nums)))
- The loop runs
Overall time complexity: O(n * log(max(nums)))
Space Complexity: O(n)
The algorithm uses:
suf_gcd
array of sizen + 1
:O(n)
suf_lcm
array of sizen + 1
:O(n)
pre_gcd
andpre_lcm
variables:O(1)
- Other variables (
ans
,i
,x
):O(1)
Total space complexity: O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in LCM Calculation
The most critical pitfall in this solution is potential integer overflow when calculating the LCM. The LCM of two numbers can grow very quickly, and multiplying it with the GCD can easily exceed integer limits.
Problem Example:
# Consider nums = [1000000, 999999, 999998] # LCM can become extremely large, causing overflow # Even with Python's arbitrary precision, the multiplication a * b in lcm() # could be problematic in other languages
Solution:
- In Python, this is less of an issue due to arbitrary precision integers, but be mindful of performance
- For other languages, consider using long long or BigInteger types
- Add overflow checking if needed:
def lcm(a: int, b: int) -> int:
if a == 0 or b == 0:
return 0
g = gcd(a, b)
# Check for potential overflow before multiplication
result = (a // g) * b # Divide first to reduce overflow risk
return result
2. Incorrect Identity Values for GCD
Using the wrong identity value for GCD operations can lead to incorrect results.
Problem:
# Wrong: Using 1 as identity for GCD prefix_gcd = 1 # This would make gcd(1, x) = 1 for any x # Correct: Using 0 as identity for GCD prefix_gcd = 0 # This makes gcd(0, x) = x
Solution:
Always use 0
as the identity for GCD operations, as gcd(0, x) = x
for any positive integer x.
3. Edge Case: Single Element Array
When the array has only one element, removing it would leave an empty array, which should return 0.
Problem:
# If nums = [5], removing the only element should give score = 0 # But suffix_gcd[1] and suffix_lcm[1] might not handle this correctly
Solution: The current implementation handles this correctly by initializing:
suffix_gcd[n] = 0
(GCD of empty set)suffix_lcm[n] = 1
(LCM of empty set)- When both are combined:
gcd(0, 0) * lcm(1, 1) = 0 * 1 = 0
4. LCM of Zero Values
If the array contains zeros, special handling is needed.
Problem:
# If nums contains 0, lcm(0, x) should be 0 # But the formula a * b // gcd(a, b) could cause division issues
Solution: The current implementation correctly handles this with the guard clause:
if a == 0 or b == 0: return 0
5. Not Considering the "No Removal" Case
A common mistake is forgetting to include the original array's score (without removing any element).
Problem:
# Wrong: Only considering cases where we remove an element
max_score = 0
for i in range(n):
# Only calculating scores with removal
...
# Correct: Initialize with the full array's score
max_score = suffix_gcd[0] * suffix_lcm[0]
Solution: Always initialize the answer with the factor score of the complete array before checking removal scenarios.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!