Given two integer arrays, nums (length n) and multipliers (length m), you need to perform m operations. In each operation i (where 0 ≤ i < m), you:
nums.multipliers[i] and add the result to your score.nums (so nums shrinks by one each step).
Your goal is to maximize your total score after exactly m operations.
Constraints:
nums[i] and multipliers[i] can be negative, zero, or positive.nums can only be used once.m multipliers in order, one per operation.1 ≤ m ≤ 10^3, m ≤ n ≤ 10^5.
The problem asks us to maximize a score by making a sequence of left/right picks from nums, each time multiplying by a given multiplier. A brute-force approach would try all possible sequences of left/right picks, but with up to 10^3 picks, this quickly becomes computationally infeasible.
Instead, we notice that at each step, the only decisions are:
m, and each pick reduces the size of nums, our state can be described by how many left picks we've made so far. The rest must be right picks.
This insight leads us to dynamic programming: we can cache the results for each state (number of left picks) to avoid recalculating subproblems. This drastically reduces the number of computations compared to brute-force.
We use dynamic programming (DP) to efficiently solve this problem. Here's how:
dp[left][op] be the maximum score achievable after making op operations, with left picks from the left side of nums (and op - left from the right).nums[left] * multipliers[op] and move to dp[left+1][op+1].nums[n-1-(op-left)] * multipliers[op] and move to dp[left][op+1].Take the maximum of both choices.
op == m, return 0 (no more picks left).(left, op) pair to avoid redundant computations.m is small, our DP table only needs to be size m x m (or even just m if we optimize).
This approach ensures we only compute each subproblem once, making it efficient even for large nums.
Input:
nums = [1, 2, 3], multipliers = [3, 2, 1]
1*3=3) or right (3*3=9).
2*2=4) or right (3*2=6).
1*2=2) or right (2*2=4).
1.
m steps, so O(2^m) time — infeasible for m = 1000.
m possible values for op (step), and for each step, up to op possible values for left (number of left picks). So total subproblems = O(m^2).
O(1) time, so total time is O(m^2).
O(m^2) for the memoization table.
The key insight is that, although nums may be very large, the number of operations (m) is small. By framing the problem as a series of left/right picks and using dynamic programming to cache overlapping subproblems, we efficiently compute the maximum possible score. The DP approach transforms an exponential brute-force process into a manageable quadratic solution, making it both elegant and practical.
from functools import lru_cache
class Solution:
def maximumScore(self, nums, multipliers):
n, m = len(nums), len(multipliers)
@lru_cache(None)
def dp(left, op):
if op == m:
return 0
right = n - 1 - (op - left)
pick_left = nums[left] * multipliers[op] + dp(left + 1, op + 1)
pick_right = nums[right] * multipliers[op] + dp(left, op + 1)
return max(pick_left, pick_right)
return dp(0, 0)
class Solution {
public:
int maximumScore(vector<int>& nums, vector<int>& multipliers) {
int n = nums.size(), m = multipliers.size();
vector<vector<int>> dp(m+1, vector<int>(m+1, INT_MIN));
function<int(int, int)> solve = [&](int left, int op) {
if (op == m) return 0;
if (dp[left][op] != INT_MIN) return dp[left][op];
int right = n - 1 - (op - left);
int pick_left = nums[left] * multipliers[op] + solve(left + 1, op + 1);
int pick_right = nums[right] * multipliers[op] + solve(left, op + 1);
return dp[left][op] = max(pick_left, pick_right);
};
return solve(0, 0);
}
};
class Solution {
public int maximumScore(int[] nums, int[] multipliers) {
int n = nums.length, m = multipliers.length;
Integer[][] memo = new Integer[m + 1][m + 1];
return dp(0, 0, nums, multipliers, memo, n, m);
}
private int dp(int left, int op, int[] nums, int[] multipliers, Integer[][] memo, int n, int m) {
if (op == m) return 0;
if (memo[left][op] != null) return memo[left][op];
int right = n - 1 - (op - left);
int pickLeft = nums[left] * multipliers[op] + dp(left + 1, op + 1, nums, multipliers, memo, n, m);
int pickRight = nums[right] * multipliers[op] + dp(left, op + 1, nums, multipliers, memo, n, m);
return memo[left][op] = Math.max(pickLeft, pickRight);
}
}
var maximumScore = function(nums, multipliers) {
const n = nums.length, m = multipliers.length;
const memo = Array.from({length: m + 1}, () => Array(m + 1).fill(undefined));
function dp(left, op) {
if (op === m) return 0;
if (memo[left][op] !== undefined) return memo[left][op];
const right = n - 1 - (op - left);
const pickLeft = nums[left] * multipliers[op] + dp(left + 1, op + 1);
const pickRight = nums[right] * multipliers[op] + dp(left, op + 1);
memo[left][op] = Math.max(pickLeft, pickRight);
return memo[left][op];
}
return dp(0, 0);
};