Problem

Definition (Range Min Query). Let a1,…,an∈Ra_1, \ldots, a_n \in \mathbb{R}. Given ℓ,r∈[n]\ell, r \in [n], ℓ≤r\ell \le r, return min⁡{aℓ,…,ar}\min \{ a_\ell, \ldots, a_r \}.

Basic Solutions

A data structure for the Range Min Query (RMQ) has complexity <p(n),q(n)>\left< p(n), q(n) \right> if it has pre-processing and query-answering time p(n)p(n) and q(n)q(n) on input instance of size nn, respectively.

A1\mathcal{A}_1 Precompute-None

Scan aℓ,…,ara_\ell, \ldots, a_r to find the min.

  • This is an <0,O(n)>\left< 0, O(n) \right> solution.

A2\mathcal{A}_2 Complete Binary Tree

Construct a complete binary tree with a1,…,ana_1, \ldots, a_n being the leaf nodes. Each internal node of the tree is augmented with the minimum value of its subtree.

  • This is an <O(n),O(log⁡n)>\left< O(n), O(\log n) \right> solution.

A3\mathcal{A}_3 Precompute-All

Create a n×nn \times n table MM. For each ℓ,r∈[n]\ell, r \in [n], ℓ≤r\ell \le r, the (ℓ,r)(\ell, r)-th entry records min⁡{aℓ,…,ar}\min \{ a_\ell, \ldots, a_r \}.

  • This is an <O(n2),O(1)>\left< O(n^2), O(1) \right> solution.
  • p(n)=O(n2)p(n) = O(n^2).

    • Dynamic Programming.
    • ∀ℓ,r∈[n],ℓ≤r\forall \ell, r \in [n], \ell \le r,M[ℓ,r]={aℓℓ=r,min⁡{M[ℓ,r−1],M[ℓ+1,r]},ℓ<r. M[\ell, r] = \begin{cases} a_\ell & \ell = r, \\ \min \{ M[\ell, r - 1], M[\ell + 1, r] \}, & \ell < r. \end{cases}
  • q(n)=O(1)q(n) = O(1).

A4\mathcal{A}_4 Sparse Table

Create a n×⌊log⁡n⌋n \times \lfloor \log n \rfloor table MM. For each ℓ∈[n],k∈[⌊log⁡n⌋]\ell \in [n], k \in \big[ \lfloor \log n \rfloor \big], r≐ℓ+(2k−1)≤nr \doteq \ell + \big( 2^k - 1 \big) \le n, the (ℓ,k)(\ell, k)-th entry records min⁡{aℓ,…,aℓ+2k−1}\min \{ a_\ell, \ldots, a_{\ell + 2^k - 1} \}.

  • This is an <O(nlog⁡n),O(1)>\left< O(n \log n), O(1) \right> solution.
  • p(n)=O(nlog⁡n)p(n) = O(n \log n).

    • Dynamic Programming.
    • For each ℓ∈[n],k∈[⌊log⁡n⌋]\ell \in [n], k \in \big[ \lfloor \log n \rfloor \big], r≐ℓ+(2k−1)≤nr \doteq \ell + \big( 2^k - 1 \big) \le n, letM[ℓ,k]={min⁡{aℓ,aℓ+1}k=1,min⁡{M[ℓ,ℓ+2k−1−1],M[r−2k−1+1,r]},ℓ<r. M[\ell, k] = \begin{cases} \min \{ a_\ell, a_{\ell + 1} \} & k = 1, \\ \min \{ M[\ell, \ell + 2^{k - 1} - 1], M[r - 2^{k - 1} + 1, r] \}, & \ell < r. \end{cases}
  • q(n)=O(1)q(n) = O(1).

Divide and Conquer

Preprocessing

  1. Pick s∈N+s \in \mathbb{N}_+, and let m=n/sm = n / s.

  2. Partition a1,…,ana_1, \ldots, a_n into mm blocks B1,…BmB_1, \ldots B_m, such that B1B_1 contains the first ss elements, B2B_2 contains the next ss elements, and so on. In general,

Bi≐{a(i−1)⋅s+1,…,ai⋅s},∀i∈[m]. B_i \doteq \{ a_{ (i - 1) \cdot s + 1 }, \ldots, a_{i \cdot s} \}, \quad \forall i \in [m].
  1. Denote b1,…,bmb_1, \ldots, b_m be the minimum element in B1,…BmB_1, \ldots B_m, respectively.

Query

  1. Given ℓ,r∈[n]\ell, r \in [n], ℓ≤r\ell \le r, consider

    • C1C_1 be the collection of indexes of the blocks Bi⊂{aℓ,…,ar}B_i \subset \{ a_\ell, \ldots, a_r \}:

      C1≐{i∈[m]:Bi⊂{aℓ,…,ar}} C_1 \doteq \{ i \in [m] : B_i \subset \{ a_\ell, \ldots, a_r \} \}

      If C1≠∅C_1 \neq \emptyset, let ℓ′=min⁡i∈C1i\ell' = \min_{i \in C_1} i and r′=max⁡i∈C1ir' = \max_{i \in C_1} i.

    • C2C_2 be the collection of indexes of the blocks BiB_i such that Bi⊈{aℓ,…,ar}B_i \nsubseteq \{ a_\ell, \ldots, a_r \} and Bi∩{aℓ,…,ar}≠∅B_i \cap \{ a_\ell, \ldots, a_r \} \neq \emptyset

      C2≐{i∈[m]:Bi∩{aℓ,…,ar}≠∅∧Bi⊈{aℓ,…,ar}}. C_2 \doteq \{ i \in [m] : B_i \cap \{ a_\ell, \ldots, a_r \} \neq \emptyset \wedge B_i \nsubseteq \{ a_\ell, \ldots, a_r \} \}.

      It must be that ∣C2∣≤2|C_2| \le 2.

  2. To return min⁡{aℓ,…,ar}\min \{ a_\ell, \ldots, a_r \}, return the minimum of

    • min⁡i∈C1bi\min_{i \in C_1} b_i, if C1≠∅C_1 \neq \emptyset.
    • min⁡i∈C2min⁡(Bi∩{aℓ,…,ar})\min_{i \in C_2} \min \big( B_i \cap \{ a_\ell, \ldots, a_r \}\big).

    Note that

    • The first one is a range-min query on b1,…,bmb_1, \ldots, b_m.
    • The second one consists of range-min queries on the blocks BiB_i for i∈C2i \in C_2

Approach One

  1. Suppose we invoke an algorithm AupA_{up} to answer the range-min query on b1,…,bmb_1, \ldots, b_m, with complexity <p1(m),q1(m)>\left< p_1(m), q_1(m) \right>.

  2. Suppose we invoke an algorithm AdownA_{down} to answer the range-min query range-min queries on the blocks BiB_i for i∈C2i \in C_2, with complexity <p2(s),q2(s)>\left< p_2(s), q_2(s) \right>.

  1. Since the set C2C_2 depends on the range min query, to answer arbitrary range min query, we need to run the pre-processing of AdownA_{down} on each block Bi,i∈[m]B_i, i \in [m].

This approach has complexity

<O(n+p1(m)+m⋅p2(s)),O(q1(m)+q2(s))>=<O(n+p1(n/s)+(n/s)⋅p2(s)),O(q1(n/s)+q2(s))>. \begin{aligned} &\left< O \big( n + p_1(m) + m \cdot p_2 (s) \big) , O \big( q_1(m) + q_2 (s) \big) \right> \\ &= \left< O \big( n + p_1(n / s) + (n / s) \cdot p_2 (s) \big), O \big( q_1(n / s) + q_2 (s) \big) \right>. \end{aligned}

Instantiation

A5\mathcal{A}_5 Two-Level Precompute-None

Choose Precompute-None A1\mathcal{A}_1 for both AupA_{up} and AdownA_{down}.

  • Total preprocessing time: O(n)O \big( n \big).
  • Total query time: O(m+s)=O(n)O \big( m + s \big) = O \big( \sqrt{n} \big), if m=s=nm = s = \sqrt{n}.

Hence, it has complexity <O(n),O(n)>\left< O \big( n \big), O \big( \sqrt{n} \big) \right>.

A6\mathcal{A}_6 Two-Level Binary Tree

This is just a complete binary tree over a1,…,ana_1, \ldots, a_n. Therefore,

  • Total preprocessing time: O(n)O \big( n \big).

  • Total query time: O(log⁡n)O \big( \log n \big).

Hence, it has complexity <O(n),O(log⁡n)>\left< O \big( n \big), O \big( \log n \big) \right>.

A7\mathcal{A}_7 Two-Level Sparse Table

We don't investigate Precompute-All, as the sparse table is always better.

Choose sparse table A3\mathcal{A}_3 for both AupA_{up} and AdownA_{down}.

  • Total preprocessing time: O(mlog⁡m+m⋅slog⁡s)O \big( m \log m + m \cdot s \log s \big).
    Since m⋅s=nm \cdot s = n, we have

    O(mlog⁡m+m⋅slog⁡s)=O(nslog⁡ns+nlog⁡s), O \Big( m \log m + m \cdot s \log s \Big) = O \Big( \frac{n}{s} \log \frac{n}{s} + n \log s \Big),

    which is minimized when

    nslog⁡ns=nlog⁡s  ⟺  log⁡n−log⁡s=slog⁡s  ⟺  log⁡n=Θ(slog⁡s)  ⟺  log⁡log⁡n=Θ(log⁡s). \begin{aligned} \frac{n}{s} \log \frac{n}{s} = n \log s &\iff \log n - \log s = s \log s \\ &\iff \log n = \Theta (s \log s ) \\ &\iff \log \log n = \Theta (\log s). \end{aligned}

    Therefore, the pre-processing time is reduced to

    O(nlog⁡log⁡n). O \Big( n \log \log n \Big).
  • Total query time: O(1+1)=O(1)O \big( 1 + 1 \big) = O \big( 1 \big).

Hence, it has complexity <O(nlog⁡log⁡n),O(1)>\left< O \big( n \log \log n \big), O \big( 1 \big) \right>.

Remark: Personally I suspect this approach is fast in practice. Since AdownA_{down} involves a linear scan of at most two blocks with size O(log⁡n)O(\log n), and linear scan is cache-friendly.

Remark: If we apply two level A7\mathcal{A}_7, then the preprocessing time is

O(mlog⁡log⁡m+m⋅slog⁡log⁡s)=O(nslog⁡log⁡ns+nlog⁡log⁡s), O \Big( m \log \log m + m \cdot s \log \log s \Big) = O \Big( \frac{n}{s} \log \log \frac{n}{s} + n \log \log s \Big),

Choosing s=log⁡log⁡ss = \log \log s minimized this to

O(nlog⁡log⁡log⁡log⁡s). O \Big( n \log \log \log \log s\Big).

Further, the query time remains O(1)O(1).

A8\mathcal{A}_8 Hybrid-Sparse Table and Precompute-None

Choose sparse table A3\mathcal{A}_3 for AupA_{up} and Precompute-None A1\mathcal{A}_1 for AdownA_{down}.

  • Total preprocessing time: O((n/s)log⁡(n/s))O \big( (n / s) \log (n / s) \big).

  • Total query time: O(1+s)O \big( 1 + s \big).

If we choose s=log⁡ns = \log n, then

  • Total preprocessing time: O(n)O \big( n \big).

  • Total query time: O(log⁡n)O \big( \log n \big).

Hence, it has complexity <O(n),O(log⁡n)>\left< O \big( n \big), O \big( \log n \big) \right>.

A9\mathcal{A}_9 Hybrid-Sparse Table and Binary Tree

Choose sparse table A3\mathcal{A}_3 for AupA_{up} and complete binary tree A2\mathcal{A}_2 for AdownA_{down}.

  • Total preprocessing time: O((n/s)log⁡(n/s)+(n/s)⋅s)O \big( (n / s) \log (n / s) + (n / s)\cdot s \big).

  • Total query time: O(1+log⁡s)O \big( 1 + \log s \big).

If we choose s=log⁡ns = \log n, then

  • Total preprocessing time: O(n)O \big( n \big).

  • Total query time: O(log⁡log⁡n)O \big( \log \log n \big).

Hence, it has complexity <O(n),O(log⁡log⁡n)>\left< O \big( n \big), O \big( \log \log n \big) \right>.

Remark: If we apply two level A9\mathcal{A}_9, then the preprocessing time is

O(n+n/s+n/s⋅s)=O(n), O \big( n + n / s + n / s \cdot s \big) = O(n),

Further, the query time is

O(log⁡log⁡(n/s)+log⁡log⁡s). O \big( \log \log (n / s) + \log \log s \big).

Unfortunately, this is still log⁡log⁡n\log \log n.

Approach Two

Can we achieve <O(n),O(1)>\left< O \big( n \big), O \big( 1 \big) \right> under approach one? Recall that approach one has complexity

<O(n+p1(n/s)+(n/s)⋅p2(s)),O(q1(n/s)+q2(s))>. \begin{aligned} \left< O \big( n + p_1(n / s) + (n / s) \cdot p_2 (s) \big), O \big( q_1(n / s) + q_2 (s) \big) \right>. \end{aligned}

Suppose we want to achieve preprocessing time O(n)O(n) and query time O(1)O(1), then p2(s)p_2(s) and q2(s)q_2(s) needs to satisfy that p2(s)=sp_2(s) = s and q2(s)=1q_2(s) = 1. But this is exactly the property we want to achieve!

It seems to be impossible to achieve the desired complexity under the divide and conquer framework.

The key is to observe that the following are two different problems.

  1. Finding a RMQ algorithm with complexity <O(n),O(1)>\left< O(n), O(1) \right>.
  2. Finding an algorithm that preprocess the all block BiB_i in O(n)O(n) time, such that the range-min query on each BiB_i can be answered in O(1)O(1) time.

For the divide-and-conquer framework to work to achieve complexity <O(n),O(1)>\left< O(n), O(1) \right>, it suffices to find an algorithm for the second problem.

Theorem. There exists some s∈Θ(log⁡n)s \in \Theta(\log n), such that we can find an algorithm that preprocess the all block BiB_i in O(n)O(n) time, such that the range-min query on each BiB_i can be answered in O(1)O(1) time.

Denote such an algorithm as A∗\mathcal{A}^*. Since s∈Θ(log⁡n)s \in \Theta(\log n), choosing sparse table algorithm A3\mathcal{A}_3 to answer the range-min query on b1,…,bmb_1, \ldots, b_m incurs additional preprocessing time O(n/s⋅log⁡(n/s))=O(n)O( n / s \cdot \log (n / s) ) = O(n). Further, answering a query requires only O(1)O(1) time.

It is left to show the construction of A∗\mathcal{A}^*.

Block Type

W.L.O.G., consider the first block B1={a1,…,as}B_1 = \{ a_{ 1 }, \ldots, a_{ s} \} be a block of ss elements. We can construct a binary tree over the elements, such that it satisfies

  1. Min Heap Property: each node of the tree is the smallest one in the subtree rooted at this node.
  2. The in-order traversal of the tree gives the array B1B_1.

Such a binary tree is called a Cartesian Tree and the type of the block.

On the other hand, suppose we are given a Cartesian Tree tree constructed on B1B_1, and suppose that the value of each node of the tree is hidden. Hence, we only know the topology or shape of the binary tree. For all 1≤ℓ≤r≤s1 \le \ell \le r \le s, we can still return the index of the minimum element among aℓ,…,asa_\ell, \ldots, a_s, purely based on the topology of the binary tree.

In this sense, the answers to the RMQ on B1B_1 is entirely determined by its corresponding Cartesian Tree.

Enumeration

This motivates the following strategy.

  1. Enumerate all possible Cartesian Trees.

  2. For each Cartesian Tree, invoke the sparse table algorithm A3\mathcal{A}_3 algorithm on this tree, so that all possible range min query for this tree can be answer in O(1)O(1) time.

  3. For each block Bi,i∈[m]B_i, i \in [m], determine its Cartesian Trees. To answer a RMQ on BiB_i, access the pre-computed result on its corresponding Cartesian Trees.

Fact. The number of distinct Cartesian Trees with size ss is given by

11+s(2ss). \frac{1}{1 + s}\binom{2s}{s}.

Therefore, enumerating the Cartesian Trees, and constructing sparse tables on them for RMQ, take time and space

11+s(2ss)⋅s⋅log⁡s=Θ(s⋅log⁡s1+s2s⋅(2s/e)2s(s⋅(s/e)s)2)=Θ(log⁡ss⋅22s). \frac{1}{1 + s}\binom{2s}{s} \cdot s \cdot \log s = \Theta \left( \frac{s \cdot \log s}{1 + s} \frac{ \sqrt{2s} \cdot \big( 2s / e \big)^{2s} }{ \Big( \sqrt{s} \cdot \big( s / e \big)^{s} \Big)^2 } \right) = \Theta \left( \frac{\log s}{\sqrt{s}} \cdot 2^{2s} \right).

Choosing s=(0.5⋅log⁡n)s = \big( 0.5 \cdot \log n \big) gives Θ(n⋅(log⁡log⁡n)/log⁡n)⊂O(n)\Theta( n \cdot \big( \log \log n \big) / \sqrt{\log n}) \subset O(n).

Determining the Block Type

In this section, we show how to construct the corresponding Cartesian Tree given the block B1B_1, and to create a unique identifier ∈O(n)\in O(n) for the Cartesian Tree.

We illustrate the idea with an example. In this example, B1=(2,4,3,1)B_1 = (2, 4, 3, 1). We will construct the Cartesian Tree, by including the elements from B1B_1 one by one. Further, we maintain a stack and a stack operation sequence, both of which are initially empty. The stack keeps elements from the right spine of the tree, and the stack operation sequence records the "in stack" and "out stack" operations as we include more elements from B1B_1 into the tree.

We first include the element 22 into the tree. Since the tree consists of only one node, it is the root of the tree. It is also the only node in the right spine of the tree, so we put 22 into the stack, and append 11 to the stack operation sequence, to record the in-stack operation.

Next we include the element 44 into the tree. Since the tree satisfies min-heap property, 44 must be the child of 22. Since the in-order traversal of the tree gives the array 2,42, 4, the node 44 must be the right child of 22. We put 44 into the stack, and append 11 to the stack operation sequence, to record the in-stack operation.

We want to include the element 33 into the existing tree. Since after inclusion, the in-order traversal of the tree gives the array 2,4,32, 4, 3, the node 33 must be in the right spine of the tree. Indeed, this is a general principle for adding a new element into the tree.

Lemma (In the Right Spine). For each j∈[s]j \in [s], let TjT_j be the Cartesian tree constructed on a1,…,aja_1, \ldots, a_j. Then aja_j be must in the right spine of TjT_j.

Proof.

  1. If aja_j has right-child in TjT_j, then it is not the last element visited by the in-order traversal of TjT_j.
  2. If aja_j has a parent node in TjT_j and is the left-child of its parent, then it is not the last element visited by the in-order traversal of TjT_j.

□\square

In this case, the right spine consists of 22 and 44. We compare 33 with 44. As it is smaller than 44, we pop 44 out of the stack. We compare 33 with 22. As it is larger than 22, we put 33 into the stack. We include 33 as the right-child of 22, and set 44 as the left child of 33. We see that after these operations, the stack still contains the nodes in the right spine of the tree. Further, the min-heap property of the tree is maintained. We append 0101 to the stack operation sequence, to record the out-stack and in-stack operations performed.

We now formally states the rules for adding an element to the right spine.

Incremental Construction Rule. For each 1≤j<s1 \le j < s, let TjT_j be the Cartesian tree constructed on a1,…,aja_1, \ldots, a_j, and suppose that a1′≤a2′…≤ak′a_1' \le a_2' \ldots \le a_k' are the elements in the right spine of TjT_j. Then aj+1a_{j + 1} is added to TjT_j as follows:

  1. If aj+1<a1′a_{j + 1} < a_1', then set a1′a_1' as the left child of aj+1a_{j + 1}.

  2. If aj+1>ak′a_{j + 1} > a_k', then set aj+1a_{j + 1} as the right child of ak′a_k'.

  3. Otherwise, there exists 1≤i<k1 \le i < k, such that ai′≤aj+1<ai+1′a_i' \le a_{j + 1} < a_{i + 1}'. Replace ai+1′a_{i + 1}' by aj+1a_{j + 1} as the right child of ai′a_i', and set ai+1′a_{i + 1}' as the left child of aj+1a_{j + 1}.

It is easy to verify that after the construction

  1. aj+1a_{j + 1} is in the right spine of the tree.
  2. The min-heap property is maintained.

We want to include the element 11 into the existing tree. Since after inclusion, the in-order traversal of the tree gives the array 2,4,3,12, 4, 3, 1, the node 11 must be in the right spine of the tree. In this case, the right spine consists of 22 and 33. As 11 is smaller than 22, we pop 33 and 22 out of the stack, put 11 into the stack, and set 22 as the left child of 11. We append 001001 to the stack operation sequence, to record the stack operations performed.

Finally, we pop 11 out of the stack, and append 00 to the stack operation sequence, to record the stack operation performed. We perform this operation, so that whatever sequence of a1,…,asa_1, \ldots, a_s, the stack operation sequence corresponding to the Cartesian Tree construction procedure stated as above has length 2s2s.

The construction also reveal an interesting fact: each Cartesian Tree with ss node corresponding a 00-11 sequence with length 2s2s, such that each prefix of the sequence contains no less 11's than 00's.

We know that

∣{t∈{0,1}2s:each prefix of t contains no less 1 than 0}∣=11+s(2ss), | \{ t \in \{0, 1\}^{2s} : \text{each prefix of } t \text{ contains no less } 1 \text{ than } 0 \} | = \frac{1}{1 + s} \binom{2s}{s},

which gives a construct proof of the number of binary trees with ss nodes.

Reference

[1] Stanford, CS 166, Range Minimum Queries