Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 19 – C++ for DSA and competitive programming

C++ binary search: lower_bound and partition_point

Use lower_bound, upper_bound, equal_range and partition_point on sorted data, binary-search on the answer over iota, and avoid off-by-one and overflow bugs.

  • Intermediate
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: The four searches land where a value is, or would go · Search the answers instead of the data · Five search mistakes that report no error · Practise. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Use lower_bound, upper_bound, equal_range and binary_search correctly on sorted data
  • Search for the answer to a problem with partition_point over views::iota
  • Avoid off-by-one, comparator and overflow mistakes in binary searches

Before you start

The standard library binary-searches a sorted range for you, in about log₂ n comparisons: about 20 for a million elements. std::lower_bound returns the first element that is not less than the value you ask for, std::upper_bound returns the first element that is greater, std::equal_range returns both at once, and std::binary_search only says whether the value is there. The first element of a partitioned range for which a test is false, where the run of elements that pass the test ends. std::partition_point finds it by halving the range. is the general form behind all four: on a range where a test holds for a run of elements at the front and fails for the rest, it returns the first element for which the test fails. Run it over view: A range that does not own its elements: it refers to elements stored elsewhere or computes them, is cheap to copy, and does its work only while it is iterated, as std::views::filter and std::views::iota do. More in “C++ views and pipelines: filter, transform, iota”, which produces the numbers from lo up to, but not including, hi, and it searches numbers instead of an array. That is Finding the smallest value that works, such as a time, a capacity or a daily limit, by halving a range of candidates with a yes-or-no test instead of searching stored data., the usual way to find the smallest value that works in a contest or an interview problem. Each function also has a std::ranges form that takes a whole range. Prefer them to a loop written by hand, because hand-written loops are where off-by-one errors, endless loops and overflowing middles come from.

The four searches land where a value is, or would go

Ten sorted marks by position, with the position that lower_bound, upper_bound and equal_range return for 42, 60 and 63.what each search returnspositionmarkreturned by035lower_bound(30)142lower_bound(42),start ofequal_range(42)242insideequal_range(42)342insideequal_range(42)458upper_bound(42),end ofequal_range(42)563lower_bound(60),upper_bound(60):60 is absent663—771upper_bound(63)888—995—10pastthe endlower_bound(99)

Where each search lands in the sorted marks of bounds.cpp

Text description of the diagram

A table with one row per position of the sorted vector of marks 35, 42, 42, 42, 58, 63, 63, 71, 88 and 95, and a last row for position 10, just past the end.

  • Position 0 holds 35. lower_bound(30) returns it, because 35 is the first mark not less than 30.
  • Position 1 holds the first 42. lower_bound(42) returns it, and equal_range(42) starts here.
  • Positions 2 and 3 hold the other two 42s, inside equal_range(42).
  • Position 4 holds 58. upper_bound(42) returns it, the first mark greater than 42, and equal_range(42) ends here, so 42 appears 4 − 1 = 3 times.
  • Position 5 holds the first 63. Both lower_bound(60) and upper_bound(60) return it: 60 is not in the vector, so both bounds point to where it would be inserted.
  • Position 7 holds 71, which upper_bound(63) returns.
  • Positions 6, 8 and 9 (63, 88 and 95) are not returned by any of these searches.
  • Position 10 is past the end, the value of marks.size(). lower_bound(99) returns it, because no mark is 99 or more.
bounds.cpp C++ · bounds.cpp
// The four binary-search functions on one sorted vector of exam marks.
#include <algorithm>
#include <print>
#include <vector>

int main() {
  const std::vector<int> marks{35, 42, 42, 42, 58, 63, 63, 71, 88, 95};

  // Positions are offsets from begin(); marks.size() means "past the end".
  auto pos = [&](auto it) { return it - marks.begin(); };

  std::println("lower_bound(42) = {}", pos(std::lower_bound(marks.begin(), marks.end(), 42)));
  std::println("upper_bound(42) = {}", pos(std::upper_bound(marks.begin(), marks.end(), 42)));
  auto [first, last] = std::equal_range(marks.begin(), marks.end(), 42);
  std::println("equal_range(42) = [{}, {}), so 42 appears {} times", pos(first), pos(last), last - first);

  // A value that is not there: both bounds point to where it would go.
  std::println("lower_bound(60) = {}, upper_bound(60) = {}",
               pos(std::ranges::lower_bound(marks, 60)), pos(std::ranges::upper_bound(marks, 60)));
  std::println("binary_search(60) = {}, binary_search(63) = {}",
               std::ranges::binary_search(marks, 60), std::ranges::binary_search(marks, 63));

  // Past every element: the "position" is marks.size().
  std::println("lower_bound(99) = {} (the size is {})", pos(std::ranges::lower_bound(marks, 99)), marks.size());

  // How many marks lie in [40, 65]? Two searches and one subtraction.
  auto from = std::ranges::lower_bound(marks, 40);
  auto to = std::ranges::upper_bound(marks, 65);
  std::println("marks from 40 to 65: {}", to - from);
}
The four searches, with both compilers Python · bounds_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra bounds.cpp -o bounds && ./bounds
g++-16 -std=c++23 -Wall -Wextra bounds.cpp -o bounds-gcc && ./bounds-gcc
""")

Output

$ clang++ -std=c++23 -Wall -Wextra bounds.cpp -o bounds && ./bounds
lower_bound(42) = 1
upper_bound(42) = 4
equal_range(42) = [1, 4), so 42 appears 3 times
lower_bound(60) = 5, upper_bound(60) = 5
binary_search(60) = false, binary_search(63) = true
lower_bound(99) = 10 (the size is 10)
marks from 40 to 65: 6
$ g++-16 -std=c++23 -Wall -Wextra bounds.cpp -o bounds-gcc && ./bounds-gcc
lower_bound(42) = 1
upper_bound(42) = 4
equal_range(42) = [1, 4), so 42 appears 3 times
lower_bound(60) = 5, upper_bound(60) = 5
binary_search(60) = false, binary_search(63) = true
lower_bound(99) = 10 (the size is 10)
marks from 40 to 65: 6

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bounds_session.py

The first 42 sits at position 1, which is what lower_bound returned, and upper_bound returned position 4, the first mark after the run of 42s. So equal_range gives the half-open range: A range written [first, last) that includes its first position and stops before its last, like a loop from marks to marks + 5. It holds last - first elements, and it is empty when first == last. More in “Pointer arithmetic, arrays and std::span” [1, 4), and its length, 3, is the number of 42s. An absent value has no element to point at: both bounds of 60 are position 5, where 60 would have to go to keep the vector sorted, and binary_search says false. When every element is smaller than the value, the bounds return the end, position 10 here, so check for the end before you read the element. Two searches also count a range of values: the marks from 40 to 65 are the ones from lower_bound(40) up to, but not including, upper_bound(65), six of them, found with about 2 log₂ n comparisons however many marks there are.

The figure runs the classic search on the same marks; it stops at the first equal mark it probes. For 60 it ends with hi = 4 just left of lo = 5, where both bounds of 60 pointed. Type 42, or your own marks:

Interactive · step through it The classic search for 60 in the ten sorted marks Each probe halves the range where the target can still be; the dimmed values are ruled out.
Array of 10: 35 42 42 42 58 63 63 71 88 95. lo at 0, hi at 9.350421422423584635636717888959lohi
  • being compared or looked at
  • visited or ruled out

Pseudo-code

  1. lo ← 0; hi ← n − 1
  2. while lo ≤ hi:
  3. mid ← lo + ⌊(hi − lo) / 2⌋
  4. if a[mid] = target: return mid
  5. if a[mid] < target: lo ← mid + 1
  6. else: hi ← mid − 1
  7. return "not found"

Look for 60. The invariant: if 60 is in the array, it is somewhere in a[lo..hi]. At the start that range is the whole array, positions 0 to 9.

When it finishes: 60 not found.

Probes 0
Positions left 10

The four searches of a sorted range
Criterion lower_bound(x)upper_bound(x)equal_range(x)binary_search(x)
Returns The first element not less than xThe first element greater than xBoth, as a pairtrue or false
When x is absent Where x would be insertedThe same positionAn empty range at that positionfalse
Comparisons at most log₂ n + O(1)log₂ n + O(1)2 log₂ n + O(1)log₂ n + O(1)
When to choose Find x, or the next value after itThe end of a run of equal valuesCount or visit every copy of xA yes-or-no membership test

All four need the range to be sorted by the same order they compare with, which is < unless you pass a comparator: A function that takes two elements and returns true when the first must come before the second, such as [](int a, int b) { return a > b; } for the highest first. Sorting algorithms and ordered containers call it for every comparison they make. More in “Essential C++ algorithms: find, sort, partition”. The std::ranges versions also take a projection: A function that a range algorithm applies to each element before comparing or testing it, such as &Student::marks in std::ranges::sort(students, {}, &Student::marks): the comparison sees two numbers, and the sort moves whole students. More in “C++ range algorithms and projections”, so a vector of records sorted by one member can be searched by that member directly. On a std::set or a std::map, call the member function lower_bound instead: the algorithm works on any iterators, but it can only step through a tree one element at a time.

Checkpoint

Check: The four searches and where they land

  1. Question 1 of 2 The sorted marks are 35, 42, 42, 42, 58, 63, 63, 71, 88 and 95, at positions 0 to 9. What position does upper_bound(60) return?

    Type a number.

    A hint

    60 is not among the marks. Where would it have to go to keep them sorted?

    Show the answer: Question 1 of 2

    Answer: 5 position

    60 is absent, so both bounds point to where it would be inserted: position 5, the first 63. The run printed lower_bound(60) = 5 and upper_bound(60) = 5, and the empty range [5, 5) says that 60 appears 0 times.

  2. Question 2 of 2 Typed into the figure, the values 42, 42, 42 with target 42 are found at position 1 by the classic search. What does std::lower_bound(v.begin(), v.end(), 42) return for the same vector?

    Choose one answer.

    A hint

    The classic search stops at the first equal value it probes. Does lower_bound stop there too?

    Show the answer: Question 2 of 2

    Answer: Position 0, the first 42

    A classic search answers "is it there?" and may stop at any copy. lower_bound answers "where does the run of values not less than 42 start?", so it returns position 0 here, as it returned position 1, the first of the three 42s, in the marks.

Search the answers instead of the data

Many problems ask for the smallest value that is big enough: the shortest time, the lowest capacity, the smallest budget. If you can test a candidate, and every value above a good one is also good, the candidates form a sorted range of answers to the question “is this too small?”: yes, yes, …, yes, then no for ever. partition_point finds the first no, and views::iota supplies the candidates without storing them. Here a student reads eight chapters in order, whole chapters only, and wants the smallest daily page limit that finishes them in three days:

min_pages.cpp C++ · min_pages.cpp
// Binary search on the answer: the smallest number of pages a day that finishes every chapter, in order, within
// the given number of days. partition_point searches the numbers themselves, so no array of candidates is built.
#include <algorithm>
#include <numeric>
#include <print>
#include <ranges>
#include <vector>

// Days needed to read the chapters in order, at most `limit` pages a day, whole chapters only.
int days_needed(const std::vector<int>& pages, long long limit) {
  int days = 1;
  long long today = 0;
  for (int p : pages) {
    if (today + p > limit) {  // this chapter starts a new day
      ++days;
      today = 0;
    }
    today += p;
  }
  return days;
}

int main() {
  const std::vector<int> pages{30, 12, 45, 22, 18, 40, 25, 9};
  const int days = 3;

  // Every answer lies in [largest chapter, all pages]: less than the largest chapter can never work,
  // and all pages in one day always does.
  const long long lo = std::ranges::max(pages);
  const long long hi = std::accumulate(pages.begin(), pages.end(), 0LL);

  int asked = 0;
  std::vector<long long> tested;  // every limit the search tries, in order
  // The predicate is true for limits that are too small, then false from the answer on:
  // partition_point returns the first limit for which it is false.
  auto too_small = [&](long long limit) {
    ++asked;
    tested.push_back(limit);
    return days_needed(pages, limit) > days;
  };
  const auto candidates = std::views::iota(lo, hi + 1);
  const long long best = *std::ranges::partition_point(candidates, too_small);

  std::println("limits from {} to {}: {} candidates", lo, hi, hi - lo + 1);
  std::println("smallest daily limit: {} pages, which takes {} days", best, days_needed(pages, best));
  std::println("one page less takes {} days", days_needed(pages, best - 1));
  std::print("limits tested, with the days each needs:");
  for (long long t : tested) std::print(" {} ({})", t, days_needed(pages, t));
  std::println("");
  std::println("the predicate was asked {} times; trying each limit from {} up would ask {}",
               asked, lo, best - lo + 1);
}
The smallest daily limit, with both compilers Python · min_pages_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra min_pages.cpp -o min_pages && ./min_pages
g++-16 -std=c++23 -Wall -Wextra min_pages.cpp -o min_pages-gcc && ./min_pages-gcc
""")

Output

$ clang++ -std=c++23 -Wall -Wextra min_pages.cpp -o min_pages && ./min_pages
limits from 45 to 201: 157 candidates
smallest daily limit: 85 pages, which takes 3 days
one page less takes 4 days
limits tested, with the days each needs: 123 (2) 84 (4) 104 (3) 94 (3) 89 (3) 87 (3) 86 (3) 85 (3)
the predicate was asked 8 times; trying each limit from 45 up would ask 41
$ g++-16 -std=c++23 -Wall -Wextra min_pages.cpp -o min_pages-gcc && ./min_pages-gcc
limits from 45 to 201: 157 candidates
smallest daily limit: 85 pages, which takes 3 days
one page less takes 4 days
limits tested, with the days each needs: 123 (2) 84 (4) 104 (3) 94 (3) 89 (3) 87 (3) 86 (3) 85 (3)
the predicate was asked 8 times; trying each limit from 45 up would ask 41

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 min_pages_session.py

The limit cannot be below the longest chapter, 45 pages, and reading all 201 pages in one day always works, so the answer lies among 157 candidates. partition_point asked the test 8 times, close to log₂ 157 ≈ 7.3, and found 85 pages: at 85 the chapters fit into three days, and at 84 they need four. Trying each limit upwards from 45 would have asked 41 times here, and the gap grows quickly: a range of a billion candidates takes 30 questions by halving. Two conditions make it correct, and both are yours to check: the test must change from yes to no exactly once along the range, and the true answer must lie inside the range you give. Step through the eight tests of the run:

Interactive · stage by stage The eight tests partition_point made to find 85 pages a day Step through the search on the 157 daily limits. Each round tests the middle limit and keeps the half that can still hold the answer.
The eight tests partition_point made to find 85 pages a day, step 10 of 10. Showing Candidates left (none left), Test the middle limit (tests 85), Days at that limit (3 days: not too small), Keep one half (the lower half), The answer (85 pages a day, 8 tests). In focus: The answer.next roundCandidates leftnone leftTest the middle limittests 85Days at that limit3 days: not too smallKeep one halfthe lower halfThe answer85 pages a day, 8 tests

The drawing shows the whole scene; its 10 steps are listed below.

Every step as text
  1. 157 candidates. views::iota hands partition_point the daily limits 45 to 201, 157 candidates, without storing any. The test asks of each limit it tries: is it too small, so that the chapters need more than 3 days?
  2. Test 1: 123. The middle of 45 to 201 is 123. At 123 pages a day the chapters fit into 2 days, so 123 is not too small: the answer is 123 or below, and 124 to 201 are dropped.
  3. Search 45 to 122. The search goes on in 45 to 122. If every one of them turns out too small, the answer is 123 itself.
  4. Test 2: 84. The middle of 45 to 122 is 84. At 84 pages a day the chapters need 4 days: too small, and so is every limit below 84. The search goes on in 85 to 122.
  5. Test 3: 104. 104 needs 3 days: not too small. The answer is 104 or below, so 85 to 103 are left.
  6. Test 4: 94. 94 needs 3 days as well, so the answer is 94 or below: 85 to 93 are left.
  7. Tests 5 and 6: 89, 87. 89 and then 87 need 3 days too. Each test halves what is left: 85 to 88, then 85 and 86.
  8. Test 7: 86. 86 needs 3 days, so only 85 is left to test.
  9. Test 8: 85. 85 needs 3 days, so it is not too small. No candidate is left, and every limit below 85 was shown too small by the test of 84.
  10. 85 pages a day. partition_point returns 85, the first limit that is not too small, after 8 tests, close to log₂ 157 ≈ 7.3. Trying each limit upwards from 45 would have asked 41 times.

Logarithm & Antilog Calculator Take log₂ of a range's size to see how many tests halving needs: log₂ 10⁹ is about 29.9, so 30.

Checkpoint

Check: Binary search on the answer with partition_point

  1. Question 1 of 2 partition_point tests the limit 123 first, and at 123 pages a day the chapters fit into 2 days, so 123 is not too small. Which limits does it drop?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 124 to 201

    The test is "too small?", true for small limits and false from the answer on. A false answer at 123 means the first false is at 123 or before it, so partition_point drops 124 to 201 and goes on in 45 to 122, keeping 123 as the answer if every one of those turns out too small.

  2. Question 2 of 2 Which of these tests can std::ranges::partition_point search correctly over std::views::iota(1, 100)?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [](int x) { return x * x < 50; }

    partition_point needs a test that holds for a run at the front of the range and fails for the rest. x × x < 50 changes from yes to no exactly once, at 8; the other three either start with no, or change more than once.

Five search mistakes that report no error

None of these reports an error:

Mistake Do this instead
A comparator that does not match the sort order A vector sorted with std::greater<> must be searched with std::greater<> too.
The algorithm on a set: std::lower_bound on a std::set makes about log₂ n comparisons, like the member function, but walks the elements one by one. Call the member function, s.lower_bound(x).
A loop that never ends: a hand-written search that keeps the middle in the range Make the middle move, or call the library.
An overflowing middle: (lo + hi) / 2 overflows when both are large. lo + (hi - lo) / 2 and std::midpoint do not, as the lesson on numeric algorithms shows.
A test that is not A yes-or-no test on ordered candidates is monotone when its answer changes only once along them: yes below some point and no from there on, as the test for a daily limit that is too small does.: if a candidate can be good while a larger one is bad, halving can skip the answer. Make sure every candidate above a good one is good too.

Checkpoint

Check: Search mistakes that report no error

  1. Question 1 of 2 s is a std::set<int>. Which call reaches the first element not less than x by walking down the tree?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: s.lower_bound(x)

    The general algorithms work on any iterators, and on a std::set or a std::map they can only step through the elements one by one. Call the member functions of the container instead: s.lower_bound, s.upper_bound and s.equal_range.

  2. Question 2 of 2 A hand-written search keeps int positions lo and hi, both near 2,000,000,000. Which middles stay inside int?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • std::midpoint(lo, hi)
    • lo + (hi - lo) / 2

    An int stops at 2,147,483,647, so the sum lo + hi of two positions near 2,000,000,000 overflows, which is undefined behaviour for a signed type. lo + (hi - lo) / 2 and std::midpoint never compute anything larger than the bounds themselves.

Practise

Start with the idea you know least; each choice explains itself.

Practice that adapts to you

Practise this lesson

10 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What does std::lower_bound(first, last, x) return on a sorted range?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: The first element that is not less than x

    lower_bound returns the first element not less than x, upper_bound the first greater than x, and the two together bracket every copy of x: equal_range returns both.

  2. Question 2 of 10 A sorted vector holds 10, 20, 20, 20, 30. What is upper_bound(20) - lower_bound(20), the difference between the two positions?

    Type a number.

    Show the answer: Question 2 of 10

    Answer: 3

    lower_bound(20) is position 1, the first 20, and upper_bound(20) is position 4, the 30, the first element greater than 20. The difference, 4 - 1 = 3, is the number of 20s, which is also the length of equal_range(20).

  3. Question 3 of 10 Which expression counts the elements of a sorted vector v that are at least 40 and at most 65?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: std::ranges::upper_bound(v, 65) - std::ranges::lower_bound(v, 40)

    The range starts at the first element not less than 40, lower_bound(40), and stops just before the first element greater than 65, upper_bound(65). lower_bound(65) would leave out the 65s, and upper_bound(40) the 40s.

  4. Question 4 of 10 A sorted vector holds 1,000,000 event times, and 100,000 queries each ask how many events fall in [a, b]. Which way answers them fastest?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: upper_bound(b) - lower_bound(a) for each query, about 40 comparisons

    Two searches count a range of values, as the lesson counted the marks from 40 to 65: lower_bound(a) is the first event at a or later, upper_bound(b) the first after b, and their difference is the count, in about 2 log₂ n comparisons.

  5. Question 5 of 10 What does std::ranges::partition_point(std::views::iota(1, 100), too_small) return, when too_small is true for small numbers and false from some number on?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: An iterator to the first number for which too_small is false

    partition_point expects every element for which the test holds to come before every element for which it fails, and returns the first element of the second group. Over iota that is the smallest number that is not too small; when every number is too small, it returns the end, 100.

  6. Question 6 of 10 partition_point searches 10⁹ candidates. At most how many times does it call the test?

    Type a number.

    A hint

    Each test halves what is left; 2³⁰ is about 1.07 × 10⁹.

    Show the answer: Question 6 of 10

    Answer: 30 tests

    2²⁹ = 536,870,912 is below 10⁹ and 2³⁰ is above it, so halving 10⁹ candidates down to one takes at most 30 tests, against up to a billion when each candidate is tried in turn.

  7. Question 7 of 10 Parcels must be shipped in the given order in 5 trips, and you want the smallest truck capacity that does it. Which range of capacities surely holds the answer?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: From the heaviest parcel to the total weight

    This is the daily limit again with trips for days: the smallest candidate that can work is the heaviest parcel, and the total weight always works. A range that holds the answer is one of the two conditions the search needs.

  8. Question 8 of 10 Which of these mistakes makes a binary search run for ever instead of answering?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: A hand-written loop that keeps the middle in the range and never makes it move

    Each mistake fails in its own quiet way: a wrong position, a slow walk, a wrong middle, or a loop whose range never shrinks, which is the only one that never answers.

  9. Question 9 of 10 A vector was sorted with std::ranges::sort(v, std::greater<>{}). How should std::ranges::lower_bound search it for 63?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: std::ranges::lower_bound(v, 63, std::greater<>{})

    A search must compare with the order the data was sorted by. With std::greater<> the elements are in decreasing order, so lower_bound with std::greater<> returns the first element that is not greater than 63, the first 63. Without the comparator the precondition is broken and the result means nothing.

  10. Question 10 of 10 A test says yes for the numbers 1 to 9, no for 10 to 12 and yes again for 13 to 30. What does std::ranges::partition_point over std::views::iota(1, 31) return with it?

    Choose one answer.

    A hint

    The first test is at the middle of the 30 numbers, 16.

    Show the answer: Question 10 of 10

    Answer: 31, the end of the range

    A test that is not monotone breaks partition_point's precondition. Here halving tests 16, 24, 28 and 30, all yes, and returns the end, so a candidate that is good while a larger one is bad has made the search skip the answer.

Binary-search bugs reproduced: wrong order, stuck loops and std::set Pro: opens with a Pro, Premium or Ultimate pass

Three mistakes cause most wrong binary searches: a search in a different order from the sort, a loop whose range stops shrinking, and the general algorithm used on a tree. Each one is reproduced below on both compilers, with the tool that catches it and the fix.

In this part:

  • Searching in the wrong order, and the debug mode that catches it
  • A loop that never ends, and the rounding that fixes it
  • One search on a std::set, two ways, measured
  • Choosing the search for each container

1 diagram · 6 runnable examples (C++, Python) · about 9 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Binary search on the answer, worked: proofs, paise and real numbers Pro: opens with a Pro, Premium or Ultimate pass

Binary search on the answer is only as correct as its test, so this part starts with the property the test must have and how to check it. Then it finds two answers that matter in practice, an instalment in whole paise and an interest rate as a real number, and ends with three problems to solve.

In this part:

  • Why the test must be monotone, with a proof for the daily limit
  • The smallest EMI in whole paise, checked against the formula
  • Real-valued answers: halving until two doubles meet
  • Your turn: three problems with sample tests

4 runnable examples (C header, C++, Python) · about 8 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher · Warm-up

    What is the difference between std::lower_bound and std::upper_bound, and what do they return for a value that is not in the range?

    On a sorted range, lower_bound(x) returns the first element that is not less than x, and upper_bound(x) the first element that is greater than x. When x is present, lower_bound points to its first copy and upper_bound just past its last copy, so the distance between them is the number of copies, which is what equal_range returns as a pair. When x is absent, both return the same position: where x would have to be inserted to keep the range sorted, which is the end when every element is smaller. Neither says whether x was found, so compare the element with x, after checking for the end, or call binary_search for a plain yes or no.

  2. Fresher to mid

    A teammate replaced s.lower_bound(x) on a std::set with std::lower_bound(s.begin(), s.end(), x) "to use the standard algorithm". A job that ran in seconds now takes over an hour. Explain why, with the cost of one search, and say what you would change.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    n print jobs with given page counts must run in order on k identical printers, each printer taking one consecutive block of jobs. Find the smallest possible number of pages on the busiest printer. Set up a binary search on the answer and show that it is correct.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A binary search over doubles looks for a time in seconds between 0 and 10^12 and loops while hi - lo > 1e-9. It finishes on small inputs and never finishes on some large ones. Explain why, and write a loop that always ends with the most precise answer a double can give.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • lower_bound finds the first element not less than a value, upper_bound the first greater, and equal_range both.
  • partition_point returns the first element for which a test fails; over views::iota it searches numbers.
  • Binary search on the answer needs a monotone test and a range that contains the answer.
  • Search with the order the data was sorted by, and use the member functions of sets and maps.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.