# Maximum expression by adding parentheses
### Problem
Suppose you are given a sequence of integers separated by + and − signs; for
example:
1+3−2−5+1−6+7
You can change the value of this expression by adding parentheses in difference places. For example:
1+3−2−5+1−6+7 = −1 (1+3−(2−5))+(1−6)+7 = 9
(1+(3−2))−(5+1)−(6+7) = −17
Describe and analyze an algorithm to compute, given a list of integers separated by + and − signs, the maximum possible value the expression can take by adding parentheses. Parentheses must be used only to group additions and subtractions; in particular, do not use them to create implicit multiplication as in 1 + 3(-2)(-5) + 1- 6 + 7 = 33.
[hint: you might find the matrix chain multiplication example in the slides inspiring for solving this]
### Algorithm Description
The maximum value of a long expression, like $Exp(1, n)$, depends on the values of its sub-expressions. For example, if the last operation splits the expression at operator $o_k$, the expression becomes: $(v_1\ ... \ v_k) \ \ o_k \ \ (v_{k+1} \ \dots \ v_n)$
The value of this depends on the values of the two sub-expressions $Exp(1, k)$ and $Exp(k+1, n)$.
* To maximize an expression like A + B, A and B must be maximized.
* To maximize an expression like A - B, A must be maximized and B must be minimized.
Because of this, we must simultaneously compute both the max and min possible values for every sub-expression.
The algorithm will use two n x n DP tables:
* $max[i][j]$: The maximum possible value of the sub-expression from $v_i$ to $v_j$.
* $min[i][j]$: The minimum possible value of the sub-expression from $v_i$ to $v_j$.
* It will also separate the given array into an operator and a number array
```
fun findMax(A) {
// calls on a function thats splits give sequence into numbers and operators
var op, nums = split_into_num_and_op(A);
var n = len(nums);
// initialize both min and max 2d arrays size n x n
var max = [][];
var min = [][];
// base case: smallest expression is a single number
for (var i = 1; i < n; i++) {
max[i][i] = num[i];
min[i][i] = num[i];
}
for (var len = 2; len < n; len++) {
// i = start index
for (var i = 1; i < (n - len + 1); i++) {
// j = end index
var j = i + len - 1;
max[i][j] = -99999;
min[i][j] = 99999;
for (var k = i; k < (j - 1); k++) {
var operation = op[k];
var maxVal, minVal;
if (operation == '+') {
maxVal = max[i][k] + max[k+1][j];
minVal = min[i][k] + min[k+1][j];
}
else if (operation == '-') {
maxVal = max[i][k] - min[k+1][j];
minVal = min[i][k] - max[k+1][j];
}
}
max[i][j] = max(max[i][j], maxVal);
min[i][j] = max(min[i][j], minVal);
}
}
return max[1,n];
}
```
### Correctness Proof
#### Base Case
The smallest sub-expression is a single number (lenght = 1). Initialized the array diagonal as the numbers itself.
For any $i$ from 0 to $n$:
* $max[i][i] = num[i]$
* $min[i][i] = num[i]$
#### Recurrence Relation
We want to fill $max[i, j]$ and $min[i, j]$ for sub-expressions of increasing length. Let the length be $len$. We go through $len$ from 2 to $n$.
For any sub-expression from $i$ to $j$ (where $j = i + len - 1$), we try every possible split point $k$ (where $i \le k < j$). This $k$ represents $op[k]$ as the *last* operation performed, splitting the problem into sub-expressions $Exp(i, k)$ and $Exp(k+1, j)$.
We initialize $max[i][j] = -99999$ and $min[i][j] = \ 99999$. Then, for each $k$:
If $op[k]$ == '+':
* The max value is $max(E(i, k)) + \ max(E(k+1, j))$.
* $maxVal\ = max[i][k] + max[k+1][j]$
* The min value is $\min(E(i,\ k)) + \min(E(k+1,\ j))$.
* $minVal\ = min[i][k] + min[k+1][j]$
If $op[k]$ == '−':
* The max value is $max(E(i,\ k)) - \min(E(k+1,\ j))$.
* $maxVal= max[i,\ k] - max[k+1,\ j]$
* The min value is $min(E(i,\ k)) - \ max(E(k+1,\ j))$.
* $minVal = min[i][k] - max[k+1][j]$
We update $max[i, j]$ and $min[i, j]$ with the best values found:
* $max[i][j] = \max(max[i][j],\ maxVal)$
* $min[i][j] = \min(min[i][j],\ minVal)$
The final answer will be the maximum value for the entire expression, which is $max[1][n]$.
### Time Complexity
We have three nested loops.
* The outer loop for length $len$ runs $O(n)$ times.
* The second loop for start index $i$ runs $O(n)$ times.
* The inner loop for split point $k$ runs $O(n)$ times in the worst case.
This gives a total time complexity of $O(n \times n \times n) = O(n^3)$.