You are given an array nums
of length 2 * n
where n
is a positive integer. In each of n
operations, you select two unused elements from nums
, calculate their greatest common divisor (GCD), multiply it by the operation number (starting from 1), and add the result to your total score. Once selected, the two elements cannot be used again. Your task is to maximize the total score after performing all n
operations.
nums
can be used only once.n
operations, pairing up all elements.The goal is to find the maximum possible score obtainable by optimally pairing and ordering the operations.
The problem asks us to maximize a sum where each term is the GCD of a pair of unused numbers, multiplied by the operation number. At first glance, it is tempting to try all possible pairings and all possible orders, but this quickly becomes infeasible as the array grows.
A brute-force approach would involve generating all possible ways to pair up the numbers, then for each pairing, try all possible orders of performing the operations. Since the number of pairings grows rapidly (roughly as the double factorial), this approach does not scale.
To optimize, we look for overlapping subproblems and optimal substructure, which suggests dynamic programming (DP). We need a way to efficiently represent which numbers have been used, and to remember the best score we can get from each state. Using a bitmask to represent the set of used/unused numbers is a powerful technique here.
We use dynamic programming with bitmasking to solve this problem efficiently. Here is the step-by-step approach:
2n
elements, we use a bitmask of length 2n
to indicate which numbers have been used (1) and which are still available (0).
dp(mask)
, where mask
indicates the current set of used numbers.
mask
), pair them, and recursively compute the best score for the updated mask.
op = used_count // 2 + 1
.
mask
in a memoization table (e.g., a hash map).
mask
has all bits set), we return 0.
nums
.
This approach ensures that we only compute each state once, and by always choosing the best available option, we maximize the total score.
Let's consider an example: nums = [1, 2, 3, 4]
n = 2
operations.
The DP with bitmasking will try all possible pairings and orders, but only once per unique state, ensuring efficiency.
2n
elements is (2n)! / (2^n * n!)
, and for each pairing, there are n!
orders. This is extremely large even for small n
.
2^{2n}
possible masks (states), but only those with even numbers of bits set are relevant.
O(n^2)
per state.
O( (2^{2n}) * n^2 )
. For n ≤ 7
(i.e., nums
length up to 14), this is feasible.
O(2^{2n})
for the memoization table.
To maximize the score after n
operations, we use dynamic programming and bitmasking to efficiently explore all possible pairings and orders. By representing used numbers with a bitmask and memoizing results, we avoid redundant computation and ensure we always choose the best available move. Precomputing GCDs further optimizes our solution. This approach elegantly solves a problem that is otherwise intractable with brute force.
from math import gcd
from functools import lru_cache
class Solution:
def maxScore(self, nums):
n = len(nums) // 2
gcds = {}
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
gcds[(i, j)] = gcd(nums[i], nums[j])
@lru_cache(maxsize=None)
def dp(mask):
used = bin(mask).count('1')
if used == len(nums):
return 0
op = used // 2 + 1
max_score = 0
for i in range(len(nums)):
if not (mask & (1 << i)):
for j in range(i + 1, len(nums)):
if not (mask & (1 << j)):
new_mask = mask | (1 << i) | (1 << j)
score = op * gcds[(i, j)] + dp(new_mask)
if score > max_score:
max_score = score
return max_score
return dp(0)
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxScore(vector<int>& nums) {
int n = nums.size() / 2;
int N = nums.size();
vector<vector<int>> gcds(N, vector<int>(N, 0));
for (int i = 0; i < N; ++i)
for (int j = i + 1; j < N; ++j)
gcds[i][j] = __gcd(nums[i], nums[j]);
unordered_map<int, int> memo;
function<int(int)> dp = [&](int mask) {
if (memo.count(mask)) return memo[mask];
int used = __builtin_popcount(mask);
if (used == N) return 0;
int op = used / 2 + 1;
int max_score = 0;
for (int i = 0; i < N; ++i) {
if (!(mask & (1 << i))) {
for (int j = i + 1; j < N; ++j) {
if (!(mask & (1 << j))) {
int new_mask = mask | (1 << i) | (1 << j);
int score = op * gcds[i][j] + dp(new_mask);
max_score = max(max_score, score);
}
}
}
}
return memo[mask] = max_score;
};
return dp(0);
}
};
import java.util.*;
class Solution {
public int maxScore(int[] nums) {
int n = nums.length / 2;
int N = nums.length;
int[][] gcds = new int[N][N];
for (int i = 0; i < N; ++i)
for (int j = i + 1; j < N; ++j)
gcds[i][j] = gcd(nums[i], nums[j]);
Map<Integer, Integer> memo = new HashMap<>();
return dp(0, nums, gcds, memo, N);
}
private int dp(int mask, int[] nums, int[][] gcds, Map<Integer, Integer> memo, int N) {
if (memo.containsKey(mask)) return memo.get(mask);
int used = Integer.bitCount(mask);
if (used == N) return 0;
int op = used / 2 + 1;
int max_score = 0;
for (int i = 0; i < N; ++i) {
if ((mask & (1 << i)) == 0) {
for (int j = i + 1; j < N; ++j) {
if ((mask & (1 << j)) == 0) {
int new_mask = mask | (1 << i) | (1 << j);
int score = op * gcds[i][j] + dp(new_mask, nums, gcds, memo, N);
if (score > max_score) max_score = score;
}
}
}
}
memo.put(mask, max_score);
return max_score;
}
private int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}
var maxScore = function(nums) {
const n = nums.length / 2;
const N = nums.length;
const gcds = Array.from({length: N}, () => Array(N).fill(0));
for (let i = 0; i < N; ++i)
for (let j = i + 1; j < N; ++j)
gcds[i][j] = gcd(nums[i], nums[j]);
const memo = new Map();
function dp(mask) {
if (memo.has(mask)) return memo.get(mask);
let used = 0, tmp = mask;
while (tmp) {
used += tmp & 1;
tmp >>= 1;
}
if (used === N) return 0;
const op = Math.floor(used / 2) + 1;
let max_score = 0;
for (let i = 0; i < N; ++i) {
if (!(mask & (1 << i))) {
for (let j = i + 1; j < N; ++j) {
if (!(mask & (1 << j))) {
const new_mask = mask | (1 << i) | (1 << j);
const score = op * gcds[i][j] + dp(new_mask);
if (score > max_score) max_score = score;
}
}
}
}
memo.set(mask, max_score);
return max_score;
}
function gcd(a, b) {
while (b) {
[a, b] = [b, a % b];
}
return a;
}
return dp(0);
};