binary search blog

Правка en42, от Semyazi, 2026-01-07 02:06:43

Introduction

Binary search is one of those algorithms that's easy to understand the idea of, but 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. Today, I'd like to share a perspective I discovered that made it easier for me to implement binary search. It makes the implementation much simpler as you will hardly even have the opportunity to make off-by-one errors. I hope you find it helpful!

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 $$$10^9$$$ operations is quite slow.

Logarithmic solution
Implementation

Generalizing Using Predicates

I hope you agree that the implementation to the above solution was very clean and essentially wrote 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 from a contiguous sequence of integers $$$\{\ell,\ell+1,\dots,r\}$$$ (where $$$\ell \lt r$$$) to $$$\{0,1\}$$$. 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 it exists).

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 $$$v \gt A[i]$$$ and $$$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 $$$v \gt A[i]$$$, 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$$$. The code looks like this:

int search(vi &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. 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

We'll apply our binary search technique of non-monotone predicates to this CSES problem. Let's imagine arranging the chairs in a line. First, notice that there will always be two adjacent chairs with the same colour (this has to do with the $$$n$$$ being odd condition). To start, we'll use this property to reduce the problem down to being on an array instead of a circle. Specifically, notice that we may assume chair $$$1$$$ and chair $$$n$$$ have different colours. If they have the same colour, then we may simply return $$$n$$$. After making this reduction, it turns out that the problem is equivalent to having an array $$$A$$$ of odd-length containing integers $$$0$$$ and $$$1$$$ such that $$$A[1]\neq A[n]$$$, and we want to find consecutive indices $$$1\leq i \lt j\leq n$$$ such that $$$A[i] = A[j]$$$. Do you see how similar this is to our original motivating problem? The only difference is that we wanted $$$A[i]\neq A[j]$$$, and we knew $$$A[1] = 0$$$ and $$$A[n] = 1$$$. An easy way to fix the former is to consider parity (even or odd) in our predicate. So, we can try: $$$P(i) : A[i] \bigoplus (i \% 2)$$$ However, we want $$$P(0) = 0$$$ and $$$P(1) = 1$$$. So, to fix that we can instead consider: $$$P(i) : A[i] \bigoplus 1 \bigoplus A[1] \bigoplus (i \% 2)$$$. It's not hard to see that $$$P(0) = 0$$$ and $$$P(1) = 1$$$ because we note that $$$A[1] \neq A[n]$$$ by our earlier observation. Now, the implementation is easy:

#include <bits/stdc++.h>
using namespace std;

int get(int i){
    cout << "? " << i << endl;
    char c;
    cin >> c;
    return c == 'R';
}
 
void answer(int i){
    cout << "! " << i << endl;
}
 
int main(){
    int n;
    cin >> n;
 
    // first, we determine if the first and last chairs are the same colour
    int c1 = get(1);
    int cn = get(n);
    if(c1 == cn){
        answer(n);
        return 0;
    }
    
    // otherwise, we binary search on our predicate
    auto P = [&](int i) -> int {
        return get(i) ^ 1 ^ c1 ^ (i % 2);
    };
 
    // template binary search
    int l = 1;
    int r = n;
 
    while((r - l) > 1){
        int m = midpoint(l, r);
        if(P(m)) r = m;
        else l = m;
    }
 
    // now, P(l) = 0 and P(r) = 1 (and they're one apart)
    // so we can report l
    answer(l);
 
    return 0;
}

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
Теги binary search, intermediate, advanced

История

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