Binary search is a search algorithm. The fundamental is that it reduces the searching range into half each time by comparing the middle element to the target. Normal binary search takes O(logn) comparisons, when n is the number of input. Space actually depends on the implementation. If not using recursion, there would be no extra space used in a regular binary search. Therefore, it normally is O(1).
Article Language: English
Example Language: Java
Reading Time: 17min
Key concept:
- Input must be in some sort of order (ascending, descending, alphabetic, positve and nagative, increased then descrease, etc.)
- Searching/calculation/operating range must be reduced each time.
- Targe must not be ruled out.
Classic Binary Search
input:
A sorted array and a target.output:
Index of the target if exits, otherwise, return -1;
Iteration
Time complexity is O(logn), where n is the size of input. Space is O(1) using iteration.
1 | public class BinarySearch { |
Recursion
Time complexity is the same as O(logn). Call stack counts as space expense, which is O(logn).
1 | public class BinarySearch { |
Search a 2D Matrix I
input:
A matrix that each row is in ascending order, and the first element of each row is greater than the last element of previous row. A int type target.output:
The coordinate of the target if exists. Otherwise, return -1.
One way to solve this problem by using binary search is convert 2D matrix into an array. Due to the property of the matrix, from left to right, top to buttom, all elements are in ascending order. Therefore, it will be able to use the classic binary search. The time complexity is O(n + m) where n is the number of row and m is the number of colmun. Space complexity is O(1). No extra data structure is used.
1 | public class Search2DMatrix { |
Search in a Unknown Size Array
input:
An unknwon size array in ascending order, you can only access the kth number by ArrayReader.get(k). A target number.output:
return the first occurrence of the target.
Only thing differnce from classic bianry search is right boundary. While checking the right boundary, set the left boundary where is the last position that the element is smaller than the target. Jumping 2 times of the current element to locate the right boundary, then the time complexity is O(logk + O(logn)) where k is the element from the left boundary to the right boundary, n is the number of elements preceding the left boundary.
The only reason jumping 2 times instead of a larger one is beacuse the final time complexity is difference only in coefficient. The size is unknwon. If there are only a few of elements in the array, jumping 10 times or 20 times would be out of bounds. It is not worth to take the risk.
1 | public class SearchUnknownSize { |
First Bad Log
The concept is using binary search to find the left and right boundary, and locate the first occurrence of the bad log.
Search Insert Position
input:
an integer array and a target number.output:
return the index if the target is found. If not, return the index where it would be if it were inserted in order.
It is easier to find a closest number to the target, then compare the element to the target,
- insert target at the position it if the element is greater or equal to the target.
- insert target after the position if the element is smaller than the target.
The time comlexity is O(logn). Space complexity is O(1).
1 | public class searchInsert { |
First Occurrence of Target
input:
an sorted int array in ascending orderoutput:
index of the first occrrence of the target. If not exist, return -1;
I’d like call it look to the left. Using binary search, if greater or equal, set the right boundary at the position. If less than the target, the element is excluded from searching range.
Time comlexity is O(logn), space is O(1).
1 | public class FirstOccurrence { |
Last Occurrence of Target
input:
an sorted int array in ascending orderoutput:
index of the last occurrence of the target. If not exisit, return -1.
I’d like call it look to the right. Similar to first occurrance. Only difference is if the element at the middle is less than or equal to the target, set the left boundary, because right result must be in the range from the position to the end. If the middle element is greater than the target, it is eliminated from the result.
Time comlexity is O(logn), space is O(1).
1 | public class LastOccurrence { |
Total Occurrence of Target
input:
an int array in ascending order and a target number.output:
return the total occurrence of the target.
One way to do it is, targeting either the first occurrence or the last occurrence of the target. The look throught it and count. The time complexity would be O(logn) for finding the position and O(k) for looping the number of target, so it is O(logn + k). Space comlexity is O(1).
Another way of calculating the number of target in the array is to get both first occurrence and the last occurrence of the larget. Using the last position minus the first position and plus 1 to get the total number of target in the array. The time complexity is O(2logn). Space Comlextiy is O(1).
Which ways is bettern actually depending on the number of target, k. If all elements in the array are the same numbers. For example, {1, 1, 1, …, 1, 1}. The first method becomes O(n). The second method remains the same. However, if k is a really small number, then the first method is better.
1 | public class TotalOccurrence { |
Search for a Range
As same as total occurance, only changes the return type to a new int[]{start, end}
.
Time and space comlexity are the same, too.
Closest Number to Target
input:
An integer array and an integer target number.output:
return the number that closest to the target.
Time complexity is O(logn). Space complexity is O(1). Using binary search and reducing the candidate to two elements. Whichever closer to target is the result.
1 | public class ClosestNumber { |
Search in Rotated Sorted Array I
input:
a sorted array is rotated at some unknown pivot and a target number. (No duplicates)output:
return the index of target if exists. Otherwise, return -1.
Since the array is still sorted in a way, it still can use binary search. The middle element has 3 situations:
- it is the target
- it is in an rotated interval (greater than the right most element)
- it is greater than the target, target could be in both side of the array.
- it is less than target, set the left boundary.
- it is in an unrotated interval (smaller than the right most element) - it is greater than the target, set the right boundary. - it is less than the target, target could be in both side of the array.
This problem actually would be more straitforwar if looking at a picture.
The time complexity is as same as classic binary search, O(logn), although it has more conditions. Space complexity is O(1).
1 | public class RoatedSortedArrayI { |
Search in Rotated Sorted Array II
input:
rotated array with duplicates.output:
index of target.
Fist of all, run a for loop searching target takes O(n). If the array is rotated with duplicates, it takes redundant checks to determine whether the middle point is in which interval. For example, {1, 1, 1, 1, 1, 0, 1, 1, 1}. The time complexity would be O(n) the worst case. Therefore, just simply run a loop.
1 | public class SearchRotateArrayII { |
Find Peak Element
input:
An integers array that the numbers in adjacent positions are different. A[0] < A[1] && A[A.length - 2] > A[A.length - 1].
Peak in an array: A[P] > A[P-1] && A[P] > A[P+1] >
output:
return any of peek positions. There might be more than one peeks.
According to the definition of the peek and the array’s feature. Returning any peek can use a binary saerch. Since the array is kind of sorted, the middle point would have three situations.
- middle point is the peek
- middle point is in the left side of a peek, then it’s in ascending order (like going uphill)
- middle point is in the right side of a peek, then it’s in descending order (like going downhill)
Time comlexity is O(logn). Space complexity is O(1).
If using ‘left + 1 < right’ as the condition of the while loop, it eliminates many checkings for index out of bounds excemption. The element always needs to compare with its neighbor, so it’s easier to keep at least three elements during the comparing. Making sure that the peek would not be ruled out guarantees the greater element to be the peek when finishing the loop.
1 | public class FindPeek { |
Maximum Number in Mountain Sequence
input:
An integer array which increase firsly and then decreses.output:
return the mountain top.
Actually the same question as find peek. T = O(logn), S = O(1). Comparing an element to its neighbor is always easier to keep at least 3 elements. It saves many index boundary check. Notice that left or right boundary could be the candidate of the result, it should not be eliminated.
1 | public class MountainTop { |
Find Minimum in Rotated orted Array I
This question is actually as same as finding the peek.
Find Minimum in Rotated orted Array II
As same as duplicates in a rotated sorted array, it is not worth to use binary search with all redundant checks.
Single Number IV
input:
an int array with all the numbers appear twice except one number which appears once and all the numbers which appear twice are next to each other.output:
find the number that appears once. 找出单身狗 🤣
Intuitive way is using a set. Beacuse of the property of set, if there exisit the element, remove the element. The one left in the set would be the result. Time comlexity is O(n), and space complexity is about O(n) during the comparing. However, this would waste the clue that “same numbers appear next to each other”. Since the intutitive way takes only O(n), optimized method ought to be better than that.
Since there is only 1 missing number, then the number element of the input must be odd.
There are 2 cases of the middle number.
- the middle number is the missing number.
- the middle number has an ajacent duplicate number.
- The adjacent number with the same value is in the left side.
- the adjacent number with the same value is in the right side.
The key point is, the side that has the odd number of element after eliminating the middle pair has the missing number. Time comlexity is O(logn). Space comlexity is O(1).
1 | public class MissingNumberIV { |
Power(x, n)
input:
two integers.output:
return the power of the first number.
There are several cases need to concern in the arithmetical opeation:
- overflow
- base is 0
- if exponent <= 0, error
- if exponent > 0, return 0
- exponent is 0, return 1
- exponent is negative, 1 / result
- exponent is positive,
- exponent is even number
- exponent is odd number
Using recursion to reduce the power. a to the power of b can be divided into a to the half of power of b times a to the half of power of b. And so on and so forth. The complexity is O(logb). Space compexity is O(logb) due to the call stack.
1 | public class PowerAB { |
Divide Two Integers
input:
two itnegersoutput:
divide two integers without using multiplications, division and modulo.
Using divisor to reduce the number of dividend to get the result. Counting how many dividend has been detracted1. Similar as doing power of two integers, using binary concept to accelerate the process2. Time complexity is O(logn) where n is the divisor and the space complexity is O(1).
1 | public class DivideTwoIntegers { |
Find Smallest Letter Greater Than Target (LeetCode 744)
input:
a char array of sorted letters containing only lowercase letters, and given a target letter target.output:
return the smallest element in the list that is larger than the given target.
Letters are ordered is ideal for binary search. Time complexity is O(logn). Space is O(1). Keep one element that is greater than the target. If the characters is less than or equal to target, target must closer to the first element in the array. Otherwise, the character is the result.
1 | public class SmallestGreaterLetter { |
<!– ### Count of Smaller Numberinput:
an integer array and a query list.output:
return the number of element in the array that are smaller than the given number in the query.