Lessons
1 · Foundations (Think Like a Programmer)
2 · Data Structures and Recursion
- 05Arrays and Array-Based Problem Solving
- 06Strings: Characters, Immutability, and Efficient Manipulation
- 07Linked Lists: Nodes, Pointers, and Pointer Reasoning
- 08Stacks and Queues
- 09Hashing: HashMap, HashSet, and Frequency Patterns
- 10Recursion and Backtracking
- 11Trees and Binary Search Trees
- 12Heaps and Priority Queues
3 · Core Algorithms and Reusable Patterns
4 · Advanced Interview Topics
Lesson 13 · Core Algorithms and Reusable Patterns
Searching and Sorting: Choose the Right Approach
Linear and binary search done right (invariants, boundaries and overflow-safe midpoints), lower and upper bound, searching on the answer, the main sorting algorithms and their trade-offs, and how to sort anything in Java with comparators.
For
Learners who know arrays, recursion and Big O and want a reliable way to search and sort, including the boundary details that interviews test.
Level
Before you start
Articles 4, 5, 10 and 12: Big O, arrays, recursion and the idea of comparators. Merge sort uses recursion.
On this page
Key takeaways
- Binary search needs sorted input and random access. It halves the search space each step, so it takes O(log n) time.
- Decide your interval convention first (closed or half-open) and keep the loop condition, the updates and the final answer consistent with it. Compute the middle as lo + (hi - lo) / 2.
- Lower bound (first index with value >= target) solves first occurrence, insertion position and counting duplicates. You can also binary search on an answer, if feasibility changes only once.
- Insertion sort is simple and good for small or nearly sorted data. Merge sort is stable with guaranteed O(n log n). Quick sort is fast on average but has an O(n^2) worst case.
- In Java, sort primitives with Arrays.sort, objects with a Comparator, and never write a comparator as a subtraction.
If someone hands you a phone directory and asks you to find a name, you do not read from page one. You open it in the middle, see whether your name comes before or after, and throw away half the book. Then you do it again. In a few moves you have found it, even in a book with a million names.
This works for one reason: the names are sorted. Sorting is what makes fast searching possible, and fast searching is why we sort. This article covers both halves. You will learn how to write binary search without the off-by-one bugs that catch even experienced programmers, how to adapt it, and how the main sorting algorithms work and when to use each.
What you will learn
#- Linear search, and when it is the right choice.
- Binary search with a clear invariant, safe midpoint, and boundary handling.
- Lower bound and upper bound, for first occurrence, insertion position and counts.
- Binary search on an answer.
- Insertion sort, merge sort and quick sort, and the vocabulary of sorting: stable, in place, comparison-based.
- How to sort in Java using
Arrays.sort, comparators and 2D arrays.
Linear search
#Linear search checks elements one by one until it finds the target or runs out. It is O(n). Use it when:
- the data is not sorted and you only search once;
- the collection is small;
- you are searching a structure that has no fast random access, such as a linked list.
If you will search the same data many times, sorting once and then using binary search (or building a HashSet) pays off.
Binary search
#Binary search works on a sorted array. Look at the middle element:
- if it equals the target, you are done;
- if it is smaller than the target, the target can only be to the right, so discard the left half;
- if it is larger, the target can only be to the left, so discard the right half.
Repeat on the remaining half. After k steps at most n / 2^k elements remain, so after about log2(n) steps there is nothing left to check. The time is O(log n): for a million elements, about 20 steps; for a billion, about 30.
Two requirements, both essential:
- The array must be sorted (in the same order your comparisons assume).
- You need random access to the middle in
O(1). Binary search on a linked list gains nothing.
Writing it correctly: choose a convention first
#Most binary search bugs come from mixing conventions. Pick one and stay consistent. In this section we use a closed interval: the target, if present, lies in a[lo..hi] with both ends included.
Invariant: if the target is in the array, it is in a[lo..hi].
- The loop runs while the interval is not empty:
lo <= hi. - The middle is
mid = lo + (hi - lo) / 2. - If
a[mid] < target, thenmidand everything left of it is too small, solo = mid + 1. - If
a[mid] > target, thenmidand everything right of it is too big, sohi = mid - 1. - When the interval becomes empty, the target is not there.
Every update excludes mid, so the interval strictly shrinks. That is why the loop must end.
The midpoint overflow
#You may have seen mid = (lo + hi) / 2. It is wrong when lo + hi exceeds Integer.MAX_VALUE. The sum wraps to a negative number, and the resulting mid is negative, causing an ArrayIndexOutOfBoundsException or worse. Writing lo + (hi - lo) / 2 avoids the problem, because hi - lo is never larger than hi. For ordinary arrays this bug is rare, but a famous version of it sat in widely-used library code for years, and interviewers like to ask about it.
// File: Main.javapublic class Main { static int binarySearch(int[] a, int target) { int lo = 0; int hi = a.length - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (a[mid] == target) { return mid; } if (a[mid] < target) { lo = mid + 1; } else { hi = mid - 1; } } return -1; } public static void main(String[] args) { int[] a = {1, 3, 5, 7, 9, 11}; System.out.println(binarySearch(a, 7) + " " + binarySearch(a, 1) + " " + binarySearch(a, 11) + " " + binarySearch(a, 4)); System.out.println(binarySearch(new int[] {}, 3) + " " + binarySearch(new int[] {5}, 5) + " " + binarySearch(new int[] {5}, 4)); int lo = 1_500_000_000; int hi = 2_000_000_000; System.out.println("(lo + hi) / 2 = " + (lo + hi) / 2); System.out.println("lo + (hi - lo) / 2 = " + (lo + (hi - lo) / 2)); }}3 0 5 -1-1 0 -1(lo + hi) / 2 = -397483648lo + (hi - lo) / 2 = 1750000000The last two lines show the overflow: lo + hi is 3.5 billion, which does not fit in an int, so the "obvious" midpoint is negative.
Dry run for target 7 in [1, 3, 5, 7, 9, 11]:
|
|
|
| Decision |
|---|---|---|---|---|
0 | 5 | 2 | 5 |
|
3 | 5 | 4 | 9 |
|
3 | 3 | 3 | 7 | found, return |
For a missing target such as 4: the interval shrinks until lo > hi, and we return -1.
Lower bound, upper bound and duplicates
#With duplicates, "find the target" is ambiguous: which one? The more useful questions are:
- Lower bound: the first index
iwitha[i] >= target. (Where wouldtargetbe inserted to keep the array sorted, before any equal elements?) - Upper bound: the first index
iwitha[i] > target.
Then:
- the first occurrence of
targetis atlowerBound, ifa[lowerBound] == target; - the last occurrence is at
upperBound - 1; - the count of
targetisupperBound - lowerBound; - the insertion position is
lowerBound.
For these we use a half-open interval [lo, hi), where hi is one past the last candidate, and the answer can be a.length (meaning "none of them qualify"). The invariant is: the answer lies in [lo, hi]. We update hi = mid (not mid - 1), because mid itself might be the answer, and we stop when lo == hi.
// File: Main.javapublic class Main { // First index i with a[i] >= target, or a.length if there is none. static int lowerBound(int[] a, int target) { int lo = 0; int hi = a.length; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (a[mid] < target) { lo = mid + 1; // mid is too small to be the answer } else { hi = mid; // mid might be the answer, keep it } } return lo; } // First index i with a[i] > target, or a.length if there is none. static int upperBound(int[] a, int target) { int lo = 0; int hi = a.length; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (a[mid] <= target) { lo = mid + 1; } else { hi = mid; } } return lo; } // Largest x such that x * x <= n. This is a binary search on the answer, not on an array. static int floorSqrt(int n) { int lo = 0; int hi = n; int best = 0; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if ((long) mid * mid <= n) { // long, so mid * mid cannot overflow best = mid; lo = mid + 1; } else { hi = mid - 1; } } return best; } public static void main(String[] args) { int[] a = {1, 2, 2, 2, 3, 5}; System.out.println("lower(2)=" + lowerBound(a, 2) + " upper(2)=" + upperBound(a, 2) + " count=" + (upperBound(a, 2) - lowerBound(a, 2))); System.out.println("insert 4 at " + lowerBound(a, 4) + ", insert 0 at " + lowerBound(a, 0) + ", insert 9 at " + lowerBound(a, 9)); System.out.println(floorSqrt(0) + " " + floorSqrt(1) + " " + floorSqrt(8) + " " + floorSqrt(16) + " " + floorSqrt(Integer.MAX_VALUE)); }}lower(2)=1 upper(2)=4 count=3insert 4 at 5, insert 0 at 0, insert 9 at 60 1 2 4 46340Walk through lowerBound(a, 2) on [1, 2, 2, 2, 3, 5]:
|
|
|
| Decision |
|---|---|---|---|---|
0 | 6 | 3 | 2 |
|
0 | 3 | 1 | 2 | not less, so |
0 | 1 | 0 | 1 |
|
1 | 1 |
|
The first 2 is at index 1. The same loop with <= in the comparison gives the upper bound, 4. So there are 4 - 1 = 3 copies of 2.
Binary search on the answer
#Binary search is not only for arrays. Whenever you can define a yes/no question about a number x that is monotonic (it is "no, no, no, ..., yes, yes, yes" as x grows), you can binary search for the boundary. floorSqrt above is the simplest example: "is x * x <= n?" is true for small x and false for large x. We will use this technique again in the exercises, for a shipping-capacity problem. The structure is always the same:
- Define the range of possible answers.
- Write a
feasible(x)check. - Binary search for the smallest (or largest) feasible
x.
Sorting: the vocabulary
#Term | Meaning |
|---|---|
In place | Uses only |
Stable | Elements that compare equal keep their original relative order |
Comparison-based | Decides order only by comparing pairs of elements. Any such sort needs |
Stability matters when you sort by one field after another. If you sort employees by name, and then (stably) by department, each department stays sorted by name.
The main algorithms
#Algorithm | Best | Average | Worst | Extra space | Stable | Notes |
|---|---|---|---|---|---|---|
Bubble sort | O(n) | O(n^2) | O(n^2) | O(1) | yes | Mainly a teaching example |
Selection sort | O(n^2) | O(n^2) | O(n^2) | O(1) | no (usual form) | Few swaps |
Insertion sort | O(n) | O(n^2) | O(n^2) | O(1) | yes | Excellent on small or nearly sorted data |
Merge sort | O(n log n) | O(n log n) | O(n log n) | O(n) | yes | Guaranteed speed, needs extra memory |
Quick sort | O(n log n) | O(n log n) | O(n^2) | O(log n) stack | no | Very fast in practice |
Heap sort | O(n log n) | O(n log n) | O(n log n) | O(1) | no | See the heaps article |
Counting sort | O(n + k) | O(n + k) | O(n + k) | O(k) | yes (usual form) | Not comparison-based. For integers in a small range |
Insertion sort
#Think of sorting a hand of playing cards. You pick up cards one at a time, and slide each into its correct place among the cards already in your hand. The left part of the array is always sorted.
Dry run on [5, 2, 4, 6, 1, 3]. The sorted part grows by one element each step:
Step | Key | Action | Sorted part | Still to process |
|---|---|---|---|---|
start |
|
| ||
1 | 2 | shift |
|
|
2 | 4 | shift |
|
|
3 | 6 | already in place |
|
|
4 | 1 | shift |
|
|
5 | 3 | shift |
| none |
On sorted input the inner while never runs, so it is O(n). On reversed input every key moves all the way, which is O(n^2).
Merge sort
#Divide and conquer: split the array in half, sort each half (recursively), then merge the two sorted halves (the same merge you wrote in the arrays article). The base case is an array of 0 or 1 elements, already sorted.
[38, 27, 43, 3] split -> [38, 27] [43, 3] split again -> [38] [27] [43] [3] merge -> [27, 38] [3, 43] merge -> [3, 27, 38, 43]There are log2(n) levels of splitting, and merging all the pieces at one level costs O(n), so the total is O(n log n) in every case. It uses a temporary array of size n. Because the merge takes from the left half on ties, it is stable.
Quick sort
#Choose a pivot. Rearrange the array (partition) so every element smaller than the pivot is on its left and every larger one on its right. The pivot is then in its final position. Sort the left and right parts recursively.
The version below (Lomuto partition) moves the pivot to the end, scans through the rest, and swaps each smaller element into a growing "smaller than pivot" region at the front. It uses the middle element as the pivot to avoid the bad case of already sorted input.
On average the splits are reasonably balanced, giving O(n log n). If the pivot is always the smallest or largest element, one side is empty and the depth is n, which is the O(n^2) worst case. This simple version also slows down on arrays with many equal values, because elements equal to the pivot all land on one side. Production sorts use better pivot rules and three-way partitioning. In an interview, mention the worst case and that a random pivot makes it unlikely.
// File: Main.javaimport java.util.Arrays;public class Main { static void insertionSort(int[] a) { for (int i = 1; i < a.length; i++) { int key = a[i]; int j = i - 1; while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; // shift larger elements one place right j--; } a[j + 1] = key; } } static void mergeSort(int[] a) { if (a.length < 2) { return; } mergeSort(a, new int[a.length], 0, a.length - 1); } private static void mergeSort(int[] a, int[] temp, int lo, int hi) { if (lo >= hi) { return; } int mid = lo + (hi - lo) / 2; mergeSort(a, temp, lo, mid); mergeSort(a, temp, mid + 1, hi); merge(a, temp, lo, mid, hi); } private static void merge(int[] a, int[] temp, int lo, int mid, int hi) { System.arraycopy(a, lo, temp, lo, hi - lo + 1); int i = lo; int j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = temp[j++]; } else if (j > hi) { a[k] = temp[i++]; } else if (temp[j] < temp[i]) { // strictly less, so ties come from the left: stable a[k] = temp[j++]; } else { a[k] = temp[i++]; } } } static void quickSort(int[] a) { quickSort(a, 0, a.length - 1); } private static void quickSort(int[] a, int lo, int hi) { if (lo >= hi) { return; } int p = partition(a, lo, hi); quickSort(a, lo, p - 1); quickSort(a, p + 1, hi); } private static int partition(int[] a, int lo, int hi) { swap(a, lo + (hi - lo) / 2, hi); // move the middle element to the end as the pivot int pivot = a[hi]; int store = lo; // everything before store is smaller than the pivot for (int i = lo; i < hi; i++) { if (a[i] < pivot) { swap(a, i, store); store++; } } swap(a, store, hi); // put the pivot in its final place return store; } private static void swap(int[] a, int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } public static void main(String[] args) { int[][] tests = { {5, 2, 4, 6, 1, 3}, {}, {7}, {3, 3, 3, 3}, {-5, 10, 0, -5, 8}, {1, 2, 3, 4, 5}, {9, 8, 7, 6, 5, 4, 3, 2, 1}, {Integer.MAX_VALUE, Integer.MIN_VALUE, 0} }; for (int[] test : tests) { int[] expected = test.clone(); Arrays.sort(expected); int[] x = test.clone(); int[] y = test.clone(); int[] z = test.clone(); insertionSort(x); mergeSort(y); quickSort(z); boolean ok = Arrays.equals(x, expected) && Arrays.equals(y, expected) && Arrays.equals(z, expected); System.out.println(ok + " " + Arrays.toString(expected)); } }}true [1, 2, 3, 4, 5, 6]true []true [7]true [3, 3, 3, 3]true [-5, -5, 0, 8, 10]true [1, 2, 3, 4, 5]true [1, 2, 3, 4, 5, 6, 7, 8, 9]true [-2147483648, 0, 2147483647]All three sorts agree with Java's built-in Arrays.sort on every test, including empty, single, all-equal, negative, already sorted, reversed, and extreme values. Comparing your own implementation against the library on a set of tricky inputs is a very good habit.
Notice temp[j] < temp[i] in the merge. We compare the values directly, not by subtraction, so no overflow (the last test includes Integer.MIN_VALUE and Integer.MAX_VALUE).
Sorting in real Java code
#In practice you use the library:
Arrays.sort(int[])sorts primitives. It uses a tuned dual-pivot quick sort. Stability does not matter for primitives, since equal numbers are indistinguishable.Arrays.sort(T[], comparator),list.sort(comparator)andCollections.sort(list)sort objects. They use TimSort, a stable merge-based algorithm that is very fast on partly-ordered data.- You cannot pass a comparator to
Arrays.sort(int[]). To sort numbers in descending order, either sort ascending and reverse, or useInteger[]withCollections.reverseOrder().
The most useful comparator tools:
Goal | Code |
|---|---|
Sort by one number field |
|
Then break ties |
|
Reverse the order |
|
Natural order |
|
Intervals by start |
|
// File: Main.javaimport java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;public class Main { static class Person { final String name; final int age; Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return name + "(" + age + ")"; } } public static void main(String[] args) { Integer[] boxed = {5, 2, 9, 1}; Arrays.sort(boxed, Collections.reverseOrder()); System.out.println(Arrays.toString(boxed)); String[] words = {"pear", "fig", "apple", "kiwi", "date"}; Arrays.sort(words, Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder())); System.out.println(Arrays.toString(words)); int[][] intervals = {{5, 8}, {1, 3}, {2, 6}, {1, 2}}; Arrays.sort(intervals, (a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1])); System.out.println(Arrays.deepToString(intervals)); List<Person> people = new ArrayList<>(List.of( new Person("Asha", 30), new Person("Ravi", 25), new Person("Meena", 30), new Person("Kiran", 25))); people.sort(Comparator.comparingInt(p -> p.age)); // stable: equal ages keep their original order System.out.println(people); }}[9, 5, 2, 1][fig, date, kiwi, pear, apple][[1, 2], [1, 3], [2, 6], [5, 8]][Ravi(25), Kiran(25), Asha(30), Meena(30)]Look at the last line. Ravi came before Kiran in the input, and both are 25, so a stable sort keeps them in that order, and likewise Asha stays before Meena. If an interviewer asks "is this sort stable?", this is what they mean.
Choosing the right approach
#Situation | Choice |
|---|---|
Small array, or one search | Linear search |
Sorted array, many searches | Binary search |
Many searches on unsorted data | Sort once, or use a |
Need the first/last position or a count in sorted data | Lower and upper bound |
Minimise or maximise a value with a monotonic yes/no test | Binary search on the answer |
General sorting in Java |
|
Need stable sorting with guaranteed | Merge sort or |
Small or nearly sorted data | Insertion sort |
Integers in a small known range | Counting sort |
Need only the top | A heap ( |
Complexity summary
#Task | Time | Extra space |
|---|---|---|
Linear search | O(n) | O(1) |
Binary search, lower bound, upper bound | O(log n) | O(1) |
Binary search on the answer | O(log(range) × cost of feasible) | O(1) |
Insertion sort | O(n^2) worst, O(n) best | O(1) |
Merge sort | O(n log n) | O(n) |
Quick sort | O(n log n) average, O(n^2) worst | O(log n) average stack |
| O(n log n) typical | O(log n) |
| O(n log n), stable | O(n) |
Edge cases and common mistakes
#- Binary search on unsorted data. It returns garbage with no error.
- Mixing interval conventions, for example
hi = a.length - 1withwhile (lo < hi), orhi = midwithlo <= hi. Pick closed or half-open and stay with it. - Infinite loops. If a branch sets
lo = midorhi = midand the interval can stop shrinking, the loop never ends. In the closed form, always usemid + 1andmid - 1. - Overflow in
(lo + hi) / 2and inmid * mid. Uselo + (hi - lo) / 2andlongfor products. - Empty and single-element arrays. Test them for every search and sort.
- Duplicates. Decide which occurrence you want, and use lower or upper bound.
- Forgetting that
Arrays.sort(int[])mutates the array. Copy first if you need the original. - Subtraction comparators, and comparators that are not consistent.
- Sorting when you only need the top
kor just the minimum. A heap or a single pass is cheaper. - Relying on the stability of
Arrays.sort(int[])or assuming quick sort is stable. Only the object sorts are stable.
Exercises
#Exercise 1
Trace
#Run the closed-interval binary search on [2, 4, 6, 8, 10, 12, 14] for the target 12, and for the target 5. Write lo, hi, mid and the decision for every step.
Exercise 2: First and last position
#Given a sorted array that may contain duplicates, return the first and last index of a target as a two-element array, or [-1, -1] if the target is not present. The time must be O(log n).
Exercise 3: Search insert position
#Return the index of the target if it is present, otherwise the index where it would be inserted to keep the array sorted. Use O(log n).
Exercise 4: Debug
#This binary search sometimes never terminates. Find the two bugs, give an input that causes the hang, and fix the code.
static int searchBuggy(int[] a, int target) { int lo = 0; int hi = a.length - 1; while (lo <= hi) { int mid = (lo + hi) / 2; if (a[mid] == target) { return mid; } if (a[mid] < target) { lo = mid; } else { hi = mid; } } return -1;}Exercise 5: Interview-style, ship packages within D days
#Packages with weights weights[0..n-1] must be shipped in the given order by a ship. Each day the ship loads packages in order, up to its capacity, and sails. Find the smallest ship capacity that gets everything shipped within D days. For weights 1 2 3 4 5 6 7 8 9 10 and D = 5, the answer is 15.
Show solutionHide solution
Target 12 in [2, 4, 6, 8, 10, 12, 14] (indexes 0 to 6):
|
|
|
| Decision |
|---|---|---|---|---|
0 | 6 | 3 | 8 |
|
4 | 6 | 5 | 12 | found, return |
Target 5:
|
|
|
| Decision |
|---|---|---|---|---|
0 | 6 | 3 | 8 |
|
0 | 2 | 1 | 4 |
|
2 | 2 | 2 | 6 |
|
2 | 1 |
|
Two successful steps for the first target and three for the second, in an array of seven elements. The number of steps is at most floor(log2(7)) + 1 = 3.
Exercise 2
Show solutionHide solution
Use the bounds from the article. The first position is lowerBound(target), provided that index is inside the array and holds the target. The last position is upperBound(target) - 1. If the target is absent, lowerBound either points past the end or at a different value, so we return [-1, -1].
// File: Main.javaimport java.util.Arrays;public class Main { static int lowerBound(int[] a, int target) { int lo = 0; int hi = a.length; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (a[mid] < target) { lo = mid + 1; } else { hi = mid; } } return lo; } static int upperBound(int[] a, int target) { int lo = 0; int hi = a.length; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (a[mid] <= target) { lo = mid + 1; } else { hi = mid; } } return lo; } static int[] firstAndLast(int[] a, int target) { int first = lowerBound(a, target); if (first == a.length || a[first] != target) { return new int[] {-1, -1}; } return new int[] {first, upperBound(a, target) - 1}; } public static void main(String[] args) { System.out.println(Arrays.toString(firstAndLast(new int[] {5, 7, 7, 8, 8, 10}, 8))); System.out.println(Arrays.toString(firstAndLast(new int[] {5, 7, 7, 8, 8, 10}, 6))); System.out.println(Arrays.toString(firstAndLast(new int[] {}, 0))); System.out.println(Arrays.toString(firstAndLast(new int[] {2, 2, 2}, 2))); System.out.println(Arrays.toString(firstAndLast(new int[] {1, 3}, 5))); }}[3, 4][-1, -1][-1, -1][0, 2][-1, -1]Two binary searches, so O(log n) time and O(1) space. The check first == a.length guards against reading past the end when the target is larger than everything.
Exercise 3
Show solutionHide solution
This is exactly lowerBound: the first index whose value is at least the target. If the target is present, that index holds it. If not, it is the position where the target belongs.
// File: Main.javapublic class Main { static int searchInsert(int[] a, int target) { int lo = 0; int hi = a.length; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (a[mid] < target) { lo = mid + 1; } else { hi = mid; } } return lo; } public static void main(String[] args) { int[] a = {1, 3, 5, 6}; System.out.println(searchInsert(a, 5) + " " + searchInsert(a, 2) + " " + searchInsert(a, 7) + " " + searchInsert(a, 0)); System.out.println(searchInsert(new int[] {}, 4)); }}2 1 4 005 is found at index 2. 2 belongs at index 1 (between 1 and 3). 7 is larger than everything, so it goes at the end (index 4). 0 goes at the front. An empty array always gives 0.
Exercise 4
Show solutionHide solution
Two bugs:
lo = midandhi = mid. The interval is closed, somidhas already been checked and must be excluded:lo = mid + 1andhi = mid - 1. As written, the interval can stop shrinking. Fora = {1, 3}andtarget = 2:lo = 0,hi = 1,mid = 0,a[0] = 1 < 2, solo = mid = 0. The state does not change, so the loop runs forever.(lo + hi) / 2can overflow for very large arrays. Uselo + (hi - lo) / 2.
To show the hang safely, the program below counts loop iterations and gives up after 1,000.
// File: Main.javapublic class Main { static String searchBuggy(int[] a, int target) { int lo = 0; int hi = a.length - 1; int iterations = 0; while (lo <= hi) { if (++iterations > 1000) { return "gave up after 1000 iterations (infinite loop)"; } int mid = (lo + hi) / 2; if (a[mid] == target) { return "found at " + mid; } if (a[mid] < target) { lo = mid; } else { hi = mid; } } return "not found"; } static int searchFixed(int[] a, int target) { int lo = 0; int hi = a.length - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (a[mid] == target) { return mid; } if (a[mid] < target) { lo = mid + 1; } else { hi = mid - 1; } } return -1; } public static void main(String[] args) { System.out.println(searchBuggy(new int[] {1, 3}, 2)); System.out.println(searchFixed(new int[] {1, 3}, 2)); System.out.println(searchFixed(new int[] {1, 3}, 3)); }}gave up after 1000 iterations (infinite loop)-11Exercise 5
Show solutionHide solution
Observation. If the capacity is c, we can check whether D days are enough with a simple greedy simulation: load packages in order until the next one would exceed c, then start a new day. Call the number of days needed days(c). As c grows, days(c) never increases, so "can ship within D days" is a monotonic yes/no question: no, no, ..., yes, yes, .... That makes it a binary search on the answer.
Range of answers. The capacity must be at least the heaviest package (max), or that package could never ship. It never needs to exceed the total weight sum, since then everything ships in one day. So search in [max, sum] for the smallest feasible capacity.
Cost. Each feasibility check is O(n). The binary search takes O(log(sum - max)) rounds. Total O(n log(sum)), extra space O(1). We use long for the sums.
// File: Main.javapublic class Main { static boolean canShip(int[] weights, int days, long capacity) { int daysUsed = 1; long load = 0; for (int w : weights) { if (load + w > capacity) { // this package does not fit, start a new day daysUsed++; load = 0; } load += w; } return daysUsed <= days; } static long shipCapacity(int[] weights, int days) { long lo = 0; long hi = 0; for (int w : weights) { lo = Math.max(lo, w); hi += w; } while (lo < hi) { long mid = lo + (hi - lo) / 2; if (canShip(weights, days, mid)) { hi = mid; // mid works, so try smaller } else { lo = mid + 1; // mid is too small } } return lo; } public static void main(String[] args) { System.out.println(shipCapacity(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 5)); System.out.println(shipCapacity(new int[] {3, 2, 2, 4, 1, 4}, 3)); System.out.println(shipCapacity(new int[] {1, 2, 3, 1, 1}, 4)); System.out.println(shipCapacity(new int[] {7}, 1)); System.out.println(shipCapacity(new int[] {5, 5, 5}, 3)); }}156375Check the first answer by hand: with capacity 15 the days are 1+2+3+4+5 = 15, 6+7 = 13, 8, 9, 10, which is 5 days. With capacity 14, the days would be 1+2+3+4 = 10 (adding 5 makes 15, which is too big), then 5+6 = 11, 7, 8, 9, 10, which is 6 days, more than 5. So 15 is the smallest that works. The search uses the half-open form (lo < hi, hi = mid) because the answer itself may be mid.
Where to go next
#- Next: Two Pointers: Solve Problems by Coordinating Indices uses sortedness in another way, to find pairs without nested loops.
- Related: the merge step here is the one from Arrays. Intervals, which you will sort by start, are the focus of Greedy Algorithms and Interval Problems. Priority queues from the heap article are the right tool when you only need the top
kand not a full sort. - Practice habit: when you write a binary search, write down your interval convention and the invariant as a comment before the loop.
Connected lessons
Linked from 4 guides
Other pages in the library that point here.
Keep reading
More in Technology →Guide · Technology
How to Learn Data Structures and Algorithms Without Burning Out
Guide · Career
The OA Landscape: Platforms, Formats, Proctoring and Rules
Guide · Career
A LinkedIn Profile That Gets Recruiter Messages

