Rethinking Binary Search with Non-Monotone Predicates

Revision en90, by Semyazi, 2026-01-16 01:22:09

Binary search is one of those algorithms that's easy to understand the idea of, but it's often hard to work out the implementation details. For example, should it be $$$\text{lo}=0$$$ or $$$\text{lo}=1$$$? Should I set $$$\text{lo}$$$ to $$$\text{mid}$$$ or $$$\text{mid}+1$$$? Is the answer going to be $$$\text{lo}$$$ or $$$\text{hi}$$$? The questions are endless. As a result, I'd like to share with you a perspective on binary search that makes it easier to implement without bugs (in my opinion), and also more general. It involves abstracting out the actual binary search step by thinking of it as a method of finding a $$$[0,1]$$$ subarray in an array of $$$0$$$'s and $$$1$$$'s. After doing this, we find that binary search can be applied to search predicates that aren't monotone, allowing it to be applied to more problems that wouldn't usually be considered binary search at a glance. I hope you find it helpful!

Also, huge thanks to my friends: Wobert and andrewtam for proofreading!

Motivating Problem

Suppose you're given an array $$$\text{A}$$$ of $$$0$$$s and $$$1$$$s indexed from $$$1$$$ to $$$n$$$. Additionally, you know:

  • $$$A[1] = 0$$$
  • $$$A[n] = 1$$$

Your goal is to find consecutive indices $$$\ell$$$ and $$$r$$$ so that:

  • $$$1\leq\ell \lt r\leq n$$$
  • $$$A[\ell]=0$$$
  • $$$A[r]=1$$$

Of course you could loop $$$\ell$$$ from $$$1$$$ to $$$n-1$$$ and compare it with $$$\ell+1$$$, but it is possible to do much better than linear.

Logarithmic solution
Implementation

Generalizing Using Predicates

I hope you agree that the implementation of the above solution is quite clean and essentially writes itself once you understand the idea. Wouldn't it be nice if every binary search problem could be solved just like this? It turns out that we can do just that! The above problem is the quintessential binary search problem. At its very core, binary search is not related to sorted arrays at all.

To generalize this, we have to define a concept called a predicate. A real predicate in math is something abstract, but for us, a predicate is a function that assigns $$$0$$$ (false) or $$$1$$$ (true) to each integer in $$$[l,r]$$$. Essentially, it allows us to create an array consisting of $$$0$$$s and $$$1$$$s as in the motivating problem: $$$[ P(\ell),P(\ell+1),\dots,P(r) ]$$$

Predicate Example

To see an example of how all of this comes together, we'll solve the problem of finding the index of a value in a sorted array; this is the usual problem that motivates binary search. Formally, you're given a sorted integer array $$$A$$$ indexed from $$$1$$$ to $$$n$$$ and an integer $$$v$$$. Your goal is to find the first (i.e. smallest) index of $$$A$$$ where $$$v$$$ occurs (for simplicity, we assume $$$v$$$ exists in $$$A$$$).

To set this problem up, we'll define the predicate: $$$P(i) := v \leq A[i]$$$. To see why this is what we want, notice that finding the first index $$$j$$$ where $$$A[j]=v$$$ is the same as finding consecutive $$$i$$$ and $$$j$$$ such that $$$A[i] \lt v\leq A[j]$$$. This works perfectly fine as long as the first occurrence of $$$v$$$ is not at the beginning of the array, this is because we wanted $$$A[i] \lt v$$$, but $$$j$$$ could be the first index. To get around this, we'll define our predicate on: $$$\{0,1,\dots,n\}$$$ and specifically set $$$P(0) = 0$$$. If it's not clear that this is what we want, consider the following example array and corresponding predicate values with $$$v=5$$$:

$$$ \begin{equation} \begin{split} A&=[1,3,4,5,6,7,9]\\ P&=[0,0,0,1,1,1,1] \end{split} \end{equation} $$$

The code looks like this:

int search(vector<int> &A, int v, int n){
	auto P = [&](int i) -> int {
		if(i == 0) return 0;
		return v <= A[i];
	};

	int l = 0;
	int r = n;
	while((r - l) > 1){
		int m = midpoint(l, r);
		if(P(m)) r = m;
		else l = m;
	}

	return r;
}

Note

It's actually not necessary to define our predicate at $$$0$$$. This is because midpoint(l, r) will always return an integer that's strictly between $$$\ell$$$ and $$$r$$$ as long as their distance is greater than $$$1$$$. Generally speaking, we don't have to define our predicate at either endpoint $$$\ell$$$ or $$$r$$$, and we may assume $$$P(\ell)=0$$$ and $$$P(r)=1$$$.

Remark

This problem gives an example of a monotone predicate. This is a predicate where if you increase the input, the output increases (non-strictly) as well. This is the kind of predicate that appears in 99% of binary search problems. With such a predicate, there can never be multiple consecutive pairs $$$\ell$$$ and $$$r$$$ such that $$$P(\ell)=0$$$ and $$$P(r)=1$$$. Hence, in these cases, our algorithm will always return the only possible answer. The same cannot be said for the next example.

CSES: Colored Chairs

Solution
Implementation

Kattis: Always Know Where Your Towel Is

We can also use the non-monotone predicate idea in this problem. I highly recommend solving (or at least knowing the idea of) CSES: Meet in the Middle first.

Solution
Implementation

Bonus Binary Search Trick

Lastly, I want to share a trick that has been useful to me in one of the discussion problems below. It turns out that we can generalize the "template binary search" algorithm introduced at the start, we don't need $$$\ell \lt r$$$. As long as they are distinct, and $$$P(\ell)=0$$$ and $$$P(r)=1$$$, then we may do binary search as follows:

while (abs(r - l) > 1) {
    int m = midpoint(l, r);
    if(P(m)) r = m;
    else l = m;
}

This could be useful in avoiding having to consider cases when $$$\ell \lt r$$$ and $$$\ell \gt r$$$, for example. To see why it works, we focus on the new case where $$$l \gt r$$$. Then, the same loop invariant as in the original proof applies, with the one modification that $$$l \gt r$$$. In the end, $$$l,r$$$ will still be consecutive as their distance will be $$$1$$$.

Conclusion

Thank you for reading! In my experience, this binary search technique is pretty unknown/under appreciated. However, it can be powerful for solving many problems in a clean/less error-prone way. Even if your predicate happens to be monotonic, I still believe thinking about binary search as the algorithm that finds a consecutive $$$[0,1]$$$ can make the implementation feel a lot more grounded. IMHO, this is how binary search should be taught :^)

Discussion Problems

I've included a few problems that can be solved using the binary search techniques discussed in this blog. Feel free to solve them and discuss your solutions in the comments :) Also, if you know of any additional problems that do a good job of showcasing these techniques, please comment and I'll add them.

Tags binary search, intermediate, advanced

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en97 English Semyazi 2026-01-16 01:38:19 0 (published)
en96 English Semyazi 2026-01-16 01:37:43 3
en95 English Semyazi 2026-01-16 01:31:37 12
en94 English Semyazi 2026-01-16 01:30:25 3
en93 English Semyazi 2026-01-16 01:30:04 24
en92 English Semyazi 2026-01-16 01:28:35 61
en91 English Semyazi 2026-01-16 01:23:06 6
en90 English Semyazi 2026-01-16 01:22:09 1
en89 English Semyazi 2026-01-16 01:21:47 19
en88 English Semyazi 2026-01-16 01:19:39 93
en87 English Semyazi 2026-01-16 01:11:32 112
en86 English Semyazi 2026-01-16 01:08:15 236
en85 English Semyazi 2026-01-16 01:05:21 32
en84 English Semyazi 2026-01-16 01:04:17 108
en83 English Semyazi 2026-01-16 01:01:25 24
en82 English Semyazi 2026-01-16 01:00:08 43
en81 English Semyazi 2026-01-16 00:58:31 47
en80 English Semyazi 2026-01-16 00:54:57 6
en79 English Semyazi 2026-01-16 00:54:10 170
en78 English Semyazi 2026-01-16 00:04:06 19
en77 English Semyazi 2026-01-16 00:02:52 245
en76 English Semyazi 2026-01-15 23:58:18 14
en75 English Semyazi 2026-01-15 23:52:12 4
en74 English Semyazi 2026-01-15 23:50:41 15
en73 English Semyazi 2026-01-15 23:47:26 109
en72 English Semyazi 2026-01-15 23:45:43 56
en71 English Semyazi 2026-01-07 04:26:52 36
en70 English Semyazi 2026-01-07 04:10:47 2
en69 English Semyazi 2026-01-07 04:08:40 419
en68 English Semyazi 2026-01-07 04:02:01 7
en67 English Semyazi 2026-01-07 04:01:21 1
en66 English Semyazi 2026-01-07 04:01:02 4
en65 English Semyazi 2026-01-07 03:59:40 12
en64 English Semyazi 2026-01-07 03:58:39 11
en63 English Semyazi 2026-01-07 03:55:34 5
en62 English Semyazi 2026-01-07 03:54:27 170
en61 English Semyazi 2026-01-07 03:50:52 2
en60 English Semyazi 2026-01-07 03:50:38 10
en59 English Semyazi 2026-01-07 03:50:07 8
en58 English Semyazi 2026-01-07 03:49:01 54
en57 English Semyazi 2026-01-07 03:47:38 81
en56 English Semyazi 2026-01-07 02:45:56 2
en55 English Semyazi 2026-01-07 02:45:25 125
en54 English Semyazi 2026-01-07 02:44:03 10
en53 English Semyazi 2026-01-07 02:41:55 12
en52 English Semyazi 2026-01-07 02:39:18 15
en51 English Semyazi 2026-01-07 02:37:59 6
en50 English Semyazi 2026-01-07 02:34:54 18
en49 English Semyazi 2026-01-07 02:33:59 711
en48 English Semyazi 2026-01-07 02:22:44 708
en47 English Semyazi 2026-01-07 02:15:53 256
en46 English Semyazi 2026-01-07 02:13:13 135
en45 English Semyazi 2026-01-07 02:09:47 15
en44 English Semyazi 2026-01-07 02:08:20 6
en43 English Semyazi 2026-01-07 02:07:20 1400
en42 English Semyazi 2026-01-07 02:06:43 8
en41 English Semyazi 2026-01-07 01:44:18 325
en40 English Semyazi 2026-01-07 01:41:26 269
en39 English Semyazi 2026-01-07 01:34:55 488
en38 English Semyazi 2026-01-07 01:30:19 586
en37 English Semyazi 2026-01-07 01:25:47 164
en36 English Semyazi 2026-01-07 01:24:30 174
en35 English Semyazi 2026-01-06 21:16:00 4269
en34 English Semyazi 2026-01-06 20:55:59 39
en33 English Semyazi 2025-12-06 03:28:26 26
en32 English Semyazi 2025-12-06 03:27:44 2 Tiny change: ';\n }\n \n // o' -> ';\n }\n \n // o'
en31 English Semyazi 2025-12-06 03:26:56 895
en30 English Semyazi 2025-12-06 03:21:25 297 Tiny change: 'predicate. So, we ca' -> 'predicate.\n\n$\bigoplus$\n\n So, we ca'
en29 English Semyazi 2025-12-06 03:15:10 1072
en28 English Semyazi 2025-12-06 03:00:52 47 Tiny change: '\dots,n\\} and speci' -> '\dots,n\\}$ and speci'
en27 English Semyazi 2025-12-06 02:59:29 966
en26 English Semyazi 2025-12-06 02:16:43 0 Tiny change: 'ray of $0$s and $1$s as in th' -> 'ray of $0$ s and $1$ s as in th'
en25 English Semyazi 2025-12-06 02:15:33 7
en24 English Semyazi 2025-12-06 02:13:50 129
en23 English Semyazi 2025-11-21 04:53:33 72
en22 English Semyazi 2025-11-21 03:26:58 1237
en21 English Semyazi 2025-11-21 03:13:17 244 Tiny change: '\nint r = 1e9;\n\nwhile' -> '\nint r = n;\n\nwhile'
en20 English Semyazi 2025-11-21 03:10:22 97
en19 English Semyazi 2025-11-21 03:09:35 36
en18 English Semyazi 2025-11-21 03:09:17 132
en17 English Semyazi 2025-11-21 03:06:56 127
en16 English Semyazi 2025-11-21 03:05:02 64
en15 English Semyazi 2025-11-21 03:03:27 698
en14 English Semyazi 2025-11-21 02:59:32 84
en13 English Semyazi 2025-11-21 02:57:43 6 Tiny change: 'te slow.\n' -> 'te slow.\naeou\n'
en12 English Semyazi 2025-11-21 02:56:41 122
en11 English Semyazi 2025-11-21 02:53:39 28
en10 English Semyazi 2025-11-21 02:51:58 1 Tiny change: 'at:\n\n- $\1\leq\ell<' -> 'at:\n\n- $1\leq\ell<'
en9 English Semyazi 2025-11-21 02:51:48 2 Tiny change: 'so that:\n- $\1\le' -> 'so that:\n\n- $\1\le'
en8 English Semyazi 2025-11-21 02:51:08 68
en7 English Semyazi 2025-11-21 02:50:23 106
en6 English Semyazi 2025-11-20 15:29:39 36
en5 English Semyazi 2025-11-20 15:27:31 178 Tiny change: 'xt{A}$ of 0s and 1s' -> 'xt{A}$ of $0$s and 1s indexed from $1$'
en4 English Semyazi 2025-11-20 15:24:12 423
en3 English Semyazi 2025-11-20 15:18:07 149
en2 English Semyazi 2025-11-20 15:16:36 225
en1 English Semyazi 2025-11-20 15:12:42 22 Initial revision (saved to drafts)