Topics › Binary Search › BS on 1D Arrays
Floor and Ceil in Sorted Array
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array arr sorted in non-decreasing order and an integer x. Find two values:
- the floor of
x: the largest element that is<= x; - the ceil of
x: the smallest element that is>= x.
Return them as a pair {floor, ceil}. If one of them does not exist (no element small enough, or no element large enough), report -1 in its place. If x itself is present, both floor and ceil equal x.
Constraints: 0 ≤ n ≤ 10^5, values and x fit in a 32-bit signed integer. The problem uses -1 as the “missing” marker, which implicitly assumes non-negative values; with negative data you would use a different sentinel (for example Integer.MIN_VALUE) or return a flag.
Example 1
arr = [3, 4, 4, 7, 8, 10], x = 5
answer = [4, 7] // 4 is the largest value <= 5, 7 is the smallest value >= 5
Example 2
arr = [3, 4, 4, 7, 8, 10], x = 4
answer = [4, 4] // x is present, so it is its own floor and ceil
Example 3
arr = [3, 4, 4, 7, 8, 10], x = 2
answer = [-1, 3] // nothing is <= 2, so there is no floor
Approach 1: Brute force, one linear pass
Intuition. Walk the array once. Every value <= x is a floor candidate, and because values only grow, the last candidate we see is the largest, so we can simply overwrite floor each time. The first value >= x is the smallest such value, so the moment we see one we record it as ceil and stop; everything after it is at least as large.
Algorithm.
- Set
floor = -1,ceil = -1. - For each value
vin order:- If
v <= x, setfloor = v. - If
v >= x, setceil = vand stop scanning.
- If
- Return
{floor, ceil}.
static int[] bruteForce(int[] arr, int x) {
int floor = -1, ceil = -1;
for (int v : arr) {
if (v <= x) floor = v; // values only grow, so the last one <= x is the floor
if (v >= x) { ceil = v; break; } // the first value >= x is the smallest such value
}
return new int[]{floor, ceil};
}
Complexity. Time O(n): at most one pass. Space O(1).
When v == x both conditions fire in the same iteration: floor becomes x, then ceil becomes x and we break, which is exactly Example 2.
Approach 2: Optimal, two binary searches
Intuition. Each of the two answers is a “first/last element satisfying a monotone predicate” question on a sorted array, so each can be found by its own binary search.
- For the floor, the predicate
arr[i] <= xistrueon a prefix andfalseafterwards:T T ... T F F ... F. We want the lastT. Whenmidis aT, recordarr[mid]as a candidate and move right to look for a larger qualifying value; when it is anF, move left. - For the ceil, the predicate
arr[i] >= xisF F ... F T T ... T. We want the firstT. Whenmidis aT, record it and move left; otherwise move right.
Both searches start with ans = -1 so that “no qualifying element” falls out naturally.
Algorithm.
- Floor search:
lo = 0,hi = n - 1,ans = -1. Whilelo <= hi: ifarr[mid] <= x, setans = arr[mid]andlo = mid + 1; elsehi = mid - 1. - Ceil search: same shape with the predicate
arr[mid] >= x, recordingans = arr[mid]and moving left viahi = mid - 1; otherwiselo = mid + 1. - Return
{floorAns, ceilAns}.
static int[] optimal(int[] arr, int x) {
return new int[]{floorBinarySearch(arr, x), ceilBinarySearch(arr, x)};
}
private static int floorBinarySearch(int[] arr, int x) {
int lo = 0, hi = arr.length - 1, ans = -1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] <= x) {
ans = arr[mid]; // candidate; a larger qualifying value may exist on the right
lo = mid + 1;
} else {
hi = mid - 1; // arr[mid] > x, so everything from mid onward is too big
}
}
return ans;
}
private static int ceilBinarySearch(int[] arr, int x) {
int lo = 0, hi = arr.length - 1, ans = -1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] >= x) {
ans = arr[mid]; // candidate; a smaller qualifying value may exist on the left
hi = mid - 1;
} else {
lo = mid + 1; // arr[mid] < x, so everything up to mid is too small
}
}
return ans;
}
Dry run of the floor search on arr = [3, 4, 4, 7, 8, 10], x = 5:
| lo | hi | mid | arr[mid] | arr[mid] <= 5? | action |
|---|---|---|---|---|---|
| 0 | 5 | 2 | 4 | yes | ans = 4, lo = 3 |
| 3 | 5 | 4 | 8 | no | hi = 3 |
| 3 | 3 | 3 | 7 | no | hi = 2 |
| 3 | 2 | lo > hi | return 4 |
The ceil search on the same input first records 8 at mid = 4, improves to 7 at mid = 3, and then finds nothing smaller that is still >= 5, returning 7.
Complexity. Time O(log n): two independent binary searches. Space O(1).
Notice the mirror symmetry between the two helpers: the floor search is the ceil search with >= flipped to <= and the directions of movement swapped. Getting one right and mirroring it is less error-prone than writing both from scratch.
Approach 3: Optimal, a single lower-bound search
Intuition. The lower bound lb of x (first index with arr[lb] >= x, or n) already pins down both answers:
- The ceil is
arr[lb]iflb < n, otherwise there is no ceil. - Everything before index
lbis strictly less thanx. So ifxis not atlb, the floor isarr[lb - 1]whenlb > 0, and does not exist whenlb == 0. Ifxis atlb, thenxis its own floor.
One binary search, then two constant-time lookups.
Algorithm.
- Compute
lbwith a half-open binary search:lo = 0,hi = n; whilelo < hi, ifarr[mid] >= xthenhi = midelselo = mid + 1. ceil = lb < n ? arr[lb] : -1.- If
lb < nandarr[lb] == x,floor = x; elsefloor = lb > 0 ? arr[lb - 1] : -1. - Return
{floor, ceil}.
static int[] optimalLowerBound(int[] arr, int x) {
int n = arr.length;
int lb = 0, hi = n; // lower bound: first index with arr[idx] >= x, or n
while (lb < hi) {
int mid = lb + (hi - lb) / 2;
if (arr[mid] >= x) hi = mid;
else lb = mid + 1;
}
int ceil = lb < n ? arr[lb] : -1;
int floor;
if (lb < n && arr[lb] == x) floor = x; // x itself is present, so it is its own floor
else floor = lb > 0 ? arr[lb - 1] : -1; // every index before lb holds a value < x
return new int[]{floor, ceil};
}
Complexity. Time O(log n) for one binary search, space O(1).
Edge cases and pitfalls
xbelow every element. No floor:{-1, arr[0]}. The lower bound is0, so thelb > 0guard matters.xabove every element. No ceil:{arr[n - 1], -1}. The lower bound isn, so never readarr[lb]without checkinglb < n.- Empty array. Both answers are
-1. In Approach 3,lb = n = 0, both guards fail, and{-1, -1}comes out without touching the array. xpresent, possibly duplicated. Floor and ceil are bothx. Approach 3 needs the explicitarr[lb] == xcheck; without it the floor would wrongly becomearr[lb - 1].- A legitimate answer of 0. The Java test
[0, 0, 0],x = 0expects{0, 0}. Do not useceil == -1as a “not yet set” flag if-1or other sentinels could be real data; the brute force here usesbreakinstead of such a flag. - Floor and ceil are values, not indices. Record
arr[mid], notmid. If a variant of the problem asks for indices, recordmidinstead and the structure stays the same. - Overflow in
mid. Uselo + (hi - lo) / 2.
Related problems
- Lower Bound is the single search that Approach 3 relies on.
- Upper Bound would give the index just past the last copy of
x, useful if you want the floor’s last index. - Search insert position returns the ceil’s index under another name.
- Search X in sorted array is the plain binary search this builds on.