#### 1. Background

##### 1.1 All nearest smaller values problem

Given an array A[0..n-1], for each element in A, search among the previous positions for the nearest position that contains a smaller value.

Formally, we need to find L[0..n-1] s.t.

`L[i] = max{j ∈ [0..i − 1] : A[j] < A[i]}`

example:

A = [1, 4, 3, 9, 10, 5]

L = [-1, 0, 0, 2, 3, 2]

When in high school, I was taught 2 algorithms to solve this problem:

- Using monotonic stack in O(n). We will get back to this topic in another post.
- One simpler algorithm just uses 2 nested loops (it is trivial that I cannot give it a name).

###### 1.1.a

```
L[0] = -1;
for(int i = 1; i < n; i++) {
int j = i - 1;
while (j != -1 && A[j] >= A[i]) j = L[j];
L[i] = j;
}
```

I prefer the second algorithm because it is simple and fast. However, for years, I could not prove its runtime complexity until recently when I encountered a paper about the LRM tree - Left-to-Right-Minima tree. Turns out, we can use an LRM tree lemma to prove its runtime of O(n).

#### 2. Left-to-Right-Minima Tree (LRM-Tree)

Consider an array A[1..n] of n value, all >= A[0] = −∞. The LRM-Tree of A is a tree with n+1 vertices, each labeled uniquely from {0, . . . , n} such that for 1 ≤ i ≤ n, L[i] is the parent node of i. The children of each node are ordered in increasing order from left to right. For example:

A[9] = 9

L[9] = 7 because A[7] = 1 is the nearest smaller element to the left of A[9]. So `L[9] = 7`

will be parent of node `9`

in LRM-Tree

###### Lemma:

Given an array A[1..n], there is an algorithm computing its LRM-Tree in at most 2(n − 1) comparisons within A.

###### Proof

We can build the LRM-Tree as follow:

- let A[0] = −∞ be the root of the tree
- scan over array A, starting from A[1], for each element A[i]: starting from the
`rightmost`

node of the tree, climb up toward the root until encountering a node x such that A[x] < A[i]. - let i be the child of x. Now i will be the new
`rightmost`

node of the tree

For example, now we are adding A[9] = 9 to the tree:

Starting from the current rightmost element `A[8] = 10`

, we follow the rightmost path and climb up toward root (the red path):

- A[8] = 10 > A[9], so we climb to its parent A[7]

- A[7] = 1 < A[9], so we stop, and let A[9] be child of A[7]. A[9] = 9 now is the new rightmost node

For each new node A[i] added to the tree, we charge it one comparison when comparing A[i] with its parent. After being added to the tree, A[i] stays on the `rightmost`

path and will be used to compare with A[i+1] at most once because after that it will be removed from the rightmost path. For example, after we use A[8] to compare with A[9], it will be removed from the red path.

Thus, each element is charged at most 2 comparisons. Finally, the last element A[n] is never scanned, and the first element A[1] is inserted without any comparison. Hence the total number of comparisons is at most 2(n-1).

We can easily see that the algorithm we use to construct the LRM-tree is the same as what we do in `1.1.a`

to find L[1..n], so the runtime of the `1.1.a`

algorithm is O(n).

From now, I will call this algorithm as `LRM-tree`

algorithm. We can use it to solve the All nearest bigger values problem also.

```
rightBigger[n-1] = -1;
for(int i = n-2; i >= 0; i--) {
int j = i + 1;
while (j != -1 && A[j] <= A[i]) j = rightBigger[j];
rightBigger[i] = j;
}
leftBigger[0] = -1;
for(int i = 1; i < n; i++) {
int j = i - 1;
while (j != -1 && A[j] <= A[i]) j = leftBigger[j];
leftBigger[i] = j;
}
```

#### Application

We can use LRM-tree, we can solve a lot of problems

- Directly use LRM-tree

- [E] https://leetcode.com/problems/next-greater-element-i/
- [M] https://leetcode.com/problems/next-greater-element-ii/
- [M] https://leetcode.com/problems/online-stock-span/
- [H] https://leetcode.com/problems/maximum-score-of-a-good-subarray/
- [H] https://leetcode.com/problems/number-of-valid-subarrays/
- [M] https://leetcode.com/problems/sum-of-subarray-minimums/
- for each element A[i], find the number of subarray having A[i] as mininum value =
`(i-L[i]) * (R[i]-i)`

- for each element A[i], find the number of subarray having A[i] as mininum value =
- [M] https://leetcode.com/problems/132-pattern/
`min[i] = min{A[0..i]}`

- for each k: if
`L[k] != -1 && min[L[k]] < A[k]`

then return true

- Histogram problem

Find the maximum rectangle in histogram: result = max(A[i] * (R[i] - L[i] - 1))

- [H] https://leetcode.com/problems/largest-rectangle-in-histogram/
- [H] https://leetcode.com/problems/maximal-rectangle/
- build the histogram for each row, the apply above problem

- [H] https://leetcode.com/problems/count-submatrices-with-all-ones/
- build the histogram for each row i,
`fx[j]`

is the number submatrices with all ones having rightmost column at column j and the bottom row at row i:`fx[j] = fx[L[j]] + histogram[j] * (j - L[j])`

- build the histogram for each row i,

- Cartesian Tree

Cartesian Tree is a binary tree derived from an array A[0..n-1] of unique values. It is built as following:

- Create a root node whose value is the minimum value in A.
- Recursively build the left subtree on the subarray prefix to the left of the minimum value.
- Recursively build the right subtree on the subarray suffix to the right of the minimum value.

Compute `leftSmaller[0..n-1]`

and `rightSmaller[0..n-1]`

of `A[0..n-1]`

using LRM-tree algorithm. Cartesian Tree has a special property: the parent of node `i`

in the Cartesian tree is either `leftSmaller[i]`

or `rightSmaller[i]`

, whichever exists and has a larger value.

Here is the code to build a Maximum Cartesian Tree. Runtime is the same as LRM-tree: O(n).

[M] https://leetcode.com/problems/maximum-binary-tree/

```
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* getNode(int index) {
if (nodeMap.find(index) != nodeMap.end()) return nodeMap[index];
auto p = new TreeNode(a[index]);
nodeMap[index] = p;
return p;
}
unordered_map<int, TreeNode*> nodeMap;
vector<int> a;
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode* head;
int ma = 0, maxIndex;
a = nums;
int n = a.size();
for(int i = 0; i < n; i++) {
if (a[i] > ma) {
ma = a[i];
maxIndex = i;
}
}
head = new TreeNode(ma);
nodeMap[maxIndex] = head;
int left[n], right[n];
left[0] = -1;
for(int i = 1; i < n; i++) {
int j = i-1;
while (j != -1 && a[j] <= a[i]) j = left[j];
left[i] = j;
}
right[n-1] = -1;
for(int i = n-2; i >= 0; i--) {
int j = i+1;
while (j != -1 && a[j] <= a[i]) j = right[j];
right[i] = j;
}
for(int i = 0; i < n; i++)
if (i != maxIndex) {
int l = left[i];
int r = right[i];
auto c = getNode(i);
if (l == -1) {
getNode(r)->left = c;
} else if (r == -1) {
getNode(l)->right = c;
} else {
if (a[l] < a[r]) {
getNode(l)->right = c;
} else {
getNode(r)->left = c;
}
}
}
return head;
}
};
```