Input:
nums = [1,2,3], multipliers = [3,2,1]
Output:
14
Explanation: An optimal solution is as follows:
- Choose from the end, [1,2,**3**], adding 3 * 3 = 9 to the score.
- Choose from the end, [1,**2**], adding 2 * 2 = 4 to the score.
- Choose from the end, [**1**], adding 1 * 1 = 1 to the score.
The total score is 9 + 4 + 1 = 14.
Example 2:
1
2
3
4
5
6
7
8
9
10
11
Input:
nums = [-5,-3,-3,-2,7,1], multipliers = [-10,-5,3,4,6]
Output:
102
Explanation: An optimal solution is as follows:
- Choose from the start, [**-5**,-3,-3,-2,7,1], adding -5 * -10 = 50 to the score.
- Choose from the start, [**-3**,-3,-2,7,1], adding -3 * -5 = 15 to the score.
- Choose from the start, [**-3**,-2,7,1], adding -3 * 3 = -9 to the score.
- Choose from the end, [-2,7,**1**], adding 1 * 4 = 4 to the score.
- Choose from the end, [-2,**7**], adding 7 * 6 = 42 to the score.
The total score is 50 + 15 - 9 + 4 + 42 = 102.
We can use two pointers, left and right, for the two ends of the array. At each step, we pick an integer x from either end of nums and multiply it by multipliers[i]. The idea is to always pick the larger x from either end.
Is this correct? Not always! Since both nums[i] and multipliers[i] can be negative (as per the constraints -1000 ≤ nums[i], multipliers[i] ≤ 1000), sometimes picking a smaller absolute value or a negative number is better, depending on the sign of multipliers[i]. For example, if multipliers[i] is -10, choosing -3 gives 30, which is better than choosing -5 and getting 50 (since we want the maximum score).
The greedy approach fails because the optimal choice at each step depends on future decisions. Dynamic programming works here because the problem has overlapping subproblems and optimal substructure: the best score for a given state depends only on the choices made so far, not on the order. By storing results for each state, we avoid redundant calculations.
classSolution {
public:int maximumScore(vector<int>& nums, vector<int>& multipliers) {
int n = nums.size(), m = multipliers.size();
vector<vector<int>> memo(m+1, vector<int>(m+1, INT_MIN));
function<int(int,int)> dfs = [&](int i, int l) {
if (i == m) return0;
if (memo[i][l] != INT_MIN) return memo[i][l];
int r = n -1- (i - l);
int left = nums[l] * multipliers[i] + dfs(i+1, l+1);
int right = nums[r] * multipliers[i] + dfs(i+1, l);
return memo[i][l] = max(left, right);
};
returndfs(0, 0);
}
};
classSolution {
publicintmaximumScore(int[] nums, int[] multipliers) {
int n = nums.length, m = multipliers.length;
Integer[][] memo =new Integer[m+1][m+1];
return dfs(nums, multipliers, 0, 0, memo);
}
privateintdfs(int[] nums, int[] multipliers, int i, int l, Integer[][] memo) {
if (i == multipliers.length) return 0;
if (memo[i][l]!=null) return memo[i][l];
int r = nums.length- 1 - (i - l);
int left = nums[l]* multipliers[i]+ dfs(nums, multipliers, i+1, l+1, memo);
int right = nums[r]* multipliers[i]+ dfs(nums, multipliers, i+1, l, memo);
return memo[i][l]= Math.max(left, right);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classSolution {
funmaximumScore(nums: IntArray, multipliers: IntArray): Int {
val n = nums.size
val m = multipliers.size
val memo = Array(m+1) { Array(m+1) { nullas Int? } }
fundfs(i: Int, l: Int): Int {
if (i == m) return0 memo[i][l]?.let { returnit }
val r = n - 1 - (i - l)
val left = nums[l] * multipliers[i] + dfs(i+1, l+1)
val right = nums[r] * multipliers[i] + dfs(i+1, l)
val ans = maxOf(left, right)
memo[i][l] = ans
return ans
}
return dfs(0, 0)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
classSolution:
defmaximumScore(self, nums: list[int], multipliers: list[int]) -> int:
n, m = len(nums), len(multipliers)
memo: dict[tuple[int, int], int] = {}
defdfs(i: int, l: int) -> int:
if i == m:
return0if (i, l) in memo:
return memo[(i, l)]
r = n -1- (i - l)
left = nums[l] * multipliers[i] + dfs(i+1, l+1)
right = nums[r] * multipliers[i] + dfs(i+1, l)
memo[(i, l)] = max(left, right)
return memo[(i, l)]
return dfs(0, 0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
impl Solution {
pubfnmaximum_score(nums: Vec<i32>, multipliers: Vec<i32>) -> i32 {
let n = nums.len();
let m = multipliers.len();
letmut memo =vec![vec![None; m+1]; m+1];
fndfs(i: usize, l: usize, nums: &Vec<i32>, multipliers: &Vec<i32>, memo: &mut Vec<Vec<Option<i32>>>) -> i32 {
if i == multipliers.len() { return0; }
iflet Some(val) = memo[i][l] { return val; }
let r = nums.len() -1- (i - l);
let left = nums[l] * multipliers[i] + dfs(i+1, l+1, nums, multipliers, memo);
let right = nums[r] * multipliers[i] + dfs(i+1, l, nums, multipliers, memo);
let ans = left.max(right);
memo[i][l] = Some(ans);
ans
}
dfs(0, 0, &nums, &multipliers, &mut memo)
}
}
This method uses bottom-up dynamic programming to avoid recursion and stack overhead. By iteratively filling a table, we build up solutions for smaller subproblems and use them to solve larger ones. The key is that the optimal score for each state depends only on the choices made so far, and we can compute these efficiently in reverse order.
classSolution {
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, 0));
for (int i = m-1; i >=0; --i) {
for (int left = i; left >=0; --left) {
int right = n -1- (i - left);
int pickLeft = nums[left] * multipliers[i] + dp[i+1][left+1];
int pickRight = nums[right] * multipliers[i] + dp[i+1][left];
dp[i][left] = max(pickLeft, pickRight);
}
}
return dp[0][0];
}
};
classSolution {
publicintmaximumScore(int[] nums, int[] multipliers) {
int n = nums.length, m = multipliers.length;
int[][] dp =newint[m+1][m+1];
for (int i = m-1; i >= 0; i--) {
for (int left = i; left >= 0; left--) {
int right = n - 1 - (i - left);
int pickLeft = nums[left]* multipliers[i]+ dp[i+1][left+1];
int pickRight = nums[right]* multipliers[i]+ dp[i+1][left];
dp[i][left]= Math.max(pickLeft, pickRight);
}
}
return dp[0][0];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
classSolution {
funmaximumScore(nums: IntArray, multipliers: IntArray): Int {
val n = nums.size
val m = multipliers.size
val dp = Array(m+1) { IntArray(m+1) }
for (i in m-1 downTo 0) {
for (left in i downTo 0) {
val right = n - 1 - (i - left)
val pickLeft = nums[left] * multipliers[i] + dp[i+1][left+1]
val pickRight = nums[right] * multipliers[i] + dp[i+1][left]
dp[i][left] = maxOf(pickLeft, pickRight)
}
}
return dp[0][0]
}
}
1
2
3
4
5
6
7
8
9
10
11
classSolution:
defmaximumScore(self, nums: list[int], multipliers: list[int]) -> int:
n, m = len(nums), len(multipliers)
dp = [[0] * (m+1) for _ in range(m+1)]
for i in range(m-1, -1, -1):
for left in range(i, -1, -1):
right = n -1- (i - left)
pick_left = nums[left] * multipliers[i] + dp[i+1][left+1]
pick_right = nums[right] * multipliers[i] + dp[i+1][left]
dp[i][left] = max(pick_left, pick_right)
return dp[0][0]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
impl Solution {
pubfnmaximum_score(nums: Vec<i32>, multipliers: Vec<i32>) -> i32 {
let n = nums.len();
let m = multipliers.len();
letmut dp =vec![vec![0; m+1]; m+1];
for i in (0..m).rev() {
for left in (0..=i).rev() {
let right = n -1- (i - left);
let pick_left = nums[left] * multipliers[i] + dp[i+1][left+1];
let pick_right = nums[right] * multipliers[i] + dp[i+1][left];
dp[i][left] = pick_left.max(pick_right);
}
}
dp[0][0]
}
}