Maximize Score After N Operations
HardUpdated: Aug 2, 2025
Practice on:
Maximize Score After N Operations Problem
Problem
You are given nums, an array of positive integers of size 2 * n. You must perform n operations on this array.
In the ith operation (1-indexed), you will:
- Choose two elements,
xandy. - Receive a score of
i * gcd(x, y). - Remove
xandyfromnums.
Return the maximum score you can receive after performing n operations.
The function gcd(x, y) is the greatest common divisor of x and y.
Examples
Example 1:
Input:
nums = [1,2]
Output:
1
Explanation: The optimal choice of operations is:
(1 * gcd(1, 2)) = 1
Example 2:
Input:
nums = [3,4,6,8]
Output:
11
Explanation: The optimal choice of operations is:
(1 * gcd(3, 6)) + (2 * gcd(4, 8)) = 3 + 8 = 11
Example 3:
Input:
nums = [1,2,3,4,5,6]
Output:
14
Explanation: The optimal choice of operations is:
(1 * gcd(1, 5)) + (2 * gcd(2, 4)) + (3 * gcd(3, 6)) = 1 + 4 + 9 = 14
Solution
Method 1 – Bitmask Dynamic Programming
Intuition
We use bitmask DP to represent which numbers have been used. For each operation, we try all pairs of unused numbers, calculate the score, and recursively solve for the remaining numbers. Memoization helps avoid recomputation.
Approach
- Precompute gcd for all pairs in nums.
- Use a DP array where dp[mask] is the max score for the current set of unused numbers (mask).
- For each mask, try all pairs of unused numbers:
- Mark them as used, calculate score for this operation, and recurse.
- Track the maximum score.
- The answer is dp[full_mask] (all numbers unused).
Code
C++
class Solution {
public:
int maxScore(vector<int>& nums) {
int n = nums.size(), m = n/2;
vector<vector<int>> g(n, vector<int>(n));
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
g[i][j] = gcd(nums[i], nums[j]);
vector<int> dp(1<<n);
for (int mask = 0; mask < (1<<n); ++mask) {
int cnt = __builtin_popcount(mask);
if (cnt % 2) continue;
for (int i = 0; i < n; ++i) {
if (!(mask & (1<<i))) continue;
for (int j = i+1; j < n; ++j) {
if (!(mask & (1<<j))) continue;
int new_mask = mask ^ (1<<i) ^ (1<<j);
int op = m - cnt/2 + 1;
dp[mask] = max(dp[mask], dp[new_mask] + op * g[i][j]);
}
}
}
return dp[(1<<n)-1];
}
};
Go
func maxScore(nums []int) int {
n, m := len(nums), len(nums)/2
g := make([][]int, n)
for i := range g {
g[i] = make([]int, n)
for j := range g[i] {
g[i][j] = gcd(nums[i], nums[j])
}
}
dp := make([]int, 1<<n)
for mask := 0; mask < 1<<n; mask++ {
cnt := bits.OnesCount(uint(mask))
if cnt%2 != 0 {
continue
}
for i := 0; i < n; i++ {
if mask&(1<<i) == 0 {
continue
}
for j := i+1; j < n; j++ {
if mask&(1<<j) == 0 {
continue
}
newMask := mask ^ (1<<i) ^ (1<<j)
op := m - cnt/2 + 1
dp[mask] = max(dp[mask], dp[newMask]+op*g[i][j])
}
}
}
return dp[(1<<n)-1]
}
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
Java
class Solution {
public int maxScore(int[] nums) {
int n = nums.length, m = n/2;
int[][] g = new int[n][n];
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
g[i][j] = gcd(nums[i], nums[j]);
int[] dp = new int[1<<n];
for (int mask = 0; mask < (1<<n); ++mask) {
int cnt = Integer.bitCount(mask);
if (cnt % 2 != 0) continue;
for (int i = 0; i < n; ++i) {
if ((mask & (1<<i)) == 0) continue;
for (int j = i+1; j < n; ++j) {
if ((mask & (1<<j)) == 0) continue;
int newMask = mask ^ (1<<i) ^ (1<<j);
int op = m - cnt/2 + 1;
dp[mask] = Math.max(dp[mask], dp[newMask] + op * g[i][j]);
}
}
}
return dp[(1<<n)-1];
}
private int gcd(int a, int b) {
while (b != 0) {
int t = b;
b = a % b;
a = t;
}
return a;
}
}
Kotlin
class Solution {
fun maxScore(nums: IntArray): Int {
val n = nums.size
val m = n/2
val g = Array(n) { IntArray(n) }
for (i in 0 until n)
for (j in 0 until n)
g[i][j] = gcd(nums[i], nums[j])
val dp = IntArray(1 shl n)
for (mask in 0 until (1 shl n)) {
val cnt = mask.countOneBits()
if (cnt % 2 != 0) continue
for (i in 0 until n) {
if (mask and (1 shl i) == 0) continue
for (j in i+1 until n) {
if (mask and (1 shl j) == 0) continue
val newMask = mask xor (1 shl i) xor (1 shl j)
val op = m - cnt/2 + 1
dp[mask] = maxOf(dp[mask], dp[newMask] + op * g[i][j])
}
}
}
return dp[(1 shl n)-1]
}
private fun gcd(a: Int, b: Int): Int {
var x = a; var y = b
while (y != 0) {
val t = y; y = x % y; x = t
}
return x
}
}
Python
class Solution:
def maxScore(self, nums: list[int]) -> int:
from math import gcd
n, m = len(nums), len(nums)//2
g = [[gcd(nums[i], nums[j]) for j in range(n)] for i in range(n)]
dp = [0] * (1<<n)
for mask in range(1<<n):
cnt = bin(mask).count('1')
if cnt % 2 != 0:
continue
for i in range(n):
if not (mask & (1<<i)):
continue
for j in range(i+1, n):
if not (mask & (1<<j)):
continue
new_mask = mask ^ (1<<i) ^ (1<<j)
op = m - cnt//2 + 1
dp[mask] = max(dp[mask], dp[new_mask] + op * g[i][j])
return dp[(1<<n)-1]
Rust
impl Solution {
pub fn max_score(nums: Vec<i32>) -> i32 {
fn gcd(mut a: i32, mut b: i32) -> i32 {
while b != 0 {
let t = b; b = a % b; a = t;
}
a
}
let n = nums.len();
let m = n/2;
let mut g = vec![vec![0; n]; n];
for i in 0..n {
for j in 0..n {
g[i][j] = gcd(nums[i], nums[j]);
}
}
let mut dp = vec![0; 1<<n];
for mask in 0..(1<<n) {
let cnt = mask.count_ones();
if cnt % 2 != 0 { continue; }
for i in 0..n {
if mask & (1<<i) == 0 { continue; }
for j in i+1..n {
if mask & (1<<j) == 0 { continue; }
let new_mask = mask ^ (1<<i) ^ (1<<j);
let op = m - cnt/2 + 1;
dp[mask as usize] = dp[mask as usize].max(dp[new_mask as usize] + op as i32 * g[i][j]);
}
}
}
dp[(1<<n)-1]
}
}
TypeScript
class Solution {
maxScore(nums: number[]): number {
const n = nums.length, m = n/2
const g = Array.from({length: n}, () => Array(n).fill(0))
for (let i = 0; i < n; ++i)
for (let j = 0; j < n; ++j)
g[i][j] = gcd(nums[i], nums[j])
const dp = Array(1<<n).fill(0)
for (let mask = 0; mask < (1<<n); ++mask) {
const cnt = countBits(mask)
if (cnt % 2 !== 0) continue
for (let i = 0; i < n; ++i) {
if (!(mask & (1<<i))) continue
for (let j = i+1; j < n; ++j) {
if (!(mask & (1<<j))) continue
const newMask = mask ^ (1<<i) ^ (1<<j)
const op = m - Math.floor(cnt/2) + 1
dp[mask] = Math.max(dp[mask], dp[newMask] + op * g[i][j])
}
}
}
return dp[(1<<n)-1]
}
}
function gcd(a: number, b: number): number {
while (b !== 0) {
[a, b] = [b, a % b]
}
return a
}
function countBits(x: number): number {
let cnt = 0
while (x) {
cnt += x & 1
x >>= 1
}
return cnt
}
Complexity
- ⏰ Time complexity:
O(n^2 * 2^n)— For each mask, we try all pairs. - 🧺 Space complexity:
O(2^n)— For the DP array.
Method 2 – Backtracking with Pruning
Intuition
Try all possible pairings using backtracking, but prune branches that cannot beat the current best score. This is slower but can be useful for small n.
Approach
- Precompute gcd for all pairs.
- Use backtracking to try all pairings:
- For each unused pair, mark as used, calculate score, and recurse.
- Track the maximum score.
- Prune branches if the remaining possible score cannot beat the current best.
- Return the best score found.
Code
Python
def maxScore(nums: list[int]) -> int:
from math import gcd
n, m = len(nums), len(nums)//2
g = [[gcd(nums[i], nums[j]) for j in range(n)] for i in range(n)]
best = 0
def dfs(op: int, used: int, score: int):
nonlocal best
if op > m:
best = max(best, score)
return
for i in range(n):
if used & (1<<i): continue
for j in range(i+1, n):
if used & (1<<j): continue
dfs(op+1, used | (1<<i) | (1<<j), score + op * g[i][j])
dfs(1, 0, 0)
return best
Complexity
- ⏰ Time complexity:
O((2n)!/(n!2^n))— All pairings, exponential. - 🧺 Space complexity:
O(n)— For recursion stack.