Maximize Score After N Operations
Leetcode Daily Challenge (14th May, 2023)
Problem Statement:-
You are given nums
, an array of positive integers of size 2 * n
. You must perform n
operations on this array.
In the i<sup>th</sup>
operation (1-indexed), you will:
Choose two elements,
x
andy
.Receive a score of
i * gcd(x, y)
.Remove
x
andy
fromnums
.
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
.
Link: https://leetcode.com/problems/maximize-score-after-n-operations/
Problem Explanation with 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
Constraints
1 <= n <= 7
nums.length == 2 * n
1 <= nums[i] <= 10<sup>6</sup>
Intuition:-
We need to keep track of what elements we have used so far, so we use a bitmask. And for the number of operations, we use a variable op.
We iterate over all possible number pairs and check whether we have used them before or not. If we have used them before we skip them else we use them and recurse them for the next operation.
We also use memoization to avoid recomputing the same state repeatedly and finally return the maximum score we can get.
Solution:-
Define a function solve(mask,op) that returns the maximum score we can get if we have used the elements in the mask and we are currently on the opth operation. Initialize a mx variable to 0 which will store the maximum score we can get.
Take a nested for loop and check if this ith and jth index have been used before or not by shifting 1 to the ith and jth position and doing a bitwise and with the mask. If they have been used before we skip them by using the continue statement.
Else we create a new mask by using the bitwise or operator on the mask and the shifted value of i and j.
Then we calculate the score we will get by using the opth operation and the gcd of the ith and jth element.
We update the mx variable by taking the maximum of the mx and the score we just calculated and the solve function with the new mask and op+1.
Finally we return the mx variable.
We call the solve function with the initial mask as 0 and the initial op as 1.
Code:-
JAVA Solution
class Solution {
public int maxScore(int[] nums) {
int n = nums.length;
int maxOps = n / 2;
int[][] memo = new int[1 << n][maxOps + 1];
return solve(nums, 0, 1, memo);
}
private int solve(int[] nums, int mask, int op, int[][] memo) {
if (op > nums.length / 2) {
return 0;
}
if (memo[mask][op] != 0) {
return memo[mask][op];
}
int maxScore = 0;
for (int i = 0; i < nums.length; i++) {
if (((1 << i) & mask) != 0) {
continue;
}
for (int j = i + 1; j < nums.length; j++) {
if (((1 << j) & mask) != 0) {
continue;
}
int newMask = mask | (1 << i) | (1 << j);
int score = op * gcd(nums[i], nums[j]);
maxScore = Math.max(maxScore, score + solve(nums, newMask, op + 1, memo));
}
}
memo[mask][op] = maxScore;
return maxScore;
}
private int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
}
Python Solution
class Solution:
def maxScore(self, nums: List[int]) -> int:
@cache
def solve(mask,op):
mx = 0
for i in range(len(nums)):
for j in range(i+1,len(nums)):
if ((1<<i) & mask) or ((1<<j) & mask):
continue
newMask = mask | (1<<i) | (1<<j)
score = op*math.gcd(nums[i],nums[j])
mx = max(mx,score+solve(newMask,op+1))
return mx
return solve(0,1)
Complexity Analysis:-
TIME:-
The time complexity is O(2^n * n^2) where n is the length of the nums array. We have 2^n states and for each state, we iterate over all possible pairs of numbers which takes n^2 time.
SPACE:-
The space complexity is O(2^n) where n is the length of the nums array. We use memoization so we need space for the 2^n states.