Algorithm Notes: Understanding of Binary Search
Background
Recently, I have recovered my algorithm praticing. It's like something is messing in my mind, maybe due to the shallow understanding before. When I tried to implement a binary search to find the first number that is not less than , I failed several times. Then I realized that I had to make it clear.
Binary Search is an extremely efficient algorithm for searching a particular number in a sorted array, as it reduces the time complexity from to .
I watched two videos which gives through explanation about binary search, but with slightly difference.
Implementation from 花花酱.
L = 0; R = N;
while (L < R) {
mid = L + (R-L)/2;
if (nums[mid] >= X)
R = mid;
else
L = mid + 1;
}
return L;
Implementation from Errichto
L = 0; R = N - 1;
ans = -1;
while (L <= R) {
mid = L + (R-L)/2;
if (nums[mid] >= X) {
ans = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
return ans;
Questions
I was confused by 3 questions.
- Why it is
mid = L + (R-L)/2
, notmid = (L+R)/2
. - Why in 花花酱‘'s version it's
R = N, while(L<R), R = mid
, while in Errichto's version it'sR = N - 1, while(L<=R), R=mid-1
. - why in 花花酱‘'s version is returned as the answer.
Explanation
The first question is easy to explain. It is to avoid the overflow of the sum of and .
The second question seemed a little bit silly after I got it clear. It's just about the current range for searching the target, and the terminaion condition shouldn't miss the very last element to be tested. For example, in 花花酱‘'s version, the search range is . So:
- starts from , which is the length of the array, not being reached by index.
- is always less than , or the while loop should be terminated.
- When updating , it means we update the search range into . is not included, as it is already been examined. The same with updating , so it should be
L=mid+1
, as shouldn't be included.
In Errichto's version, the range is . I will save the explanation here.
The third question is really where the tricky is. First I tried to understand Errichto's version, which is quite straight forward. It narrows the search range gradually and updates the answer value with the each time it is not less than , utils there is no more value to search. While the right boundary reduces, the answer approaches to the smallest value that is not less than . Let's take an example:
nums: 2, 3, 5, 6, 8, 10, 12
X: 4
first loop: L = 0, R = 6, mid = 3, ans = 6
second loop: L = 0, R = 2, mid = 1, ans = 6
third loop: L = 2, R = 2, mid = 2, ans = 5
out of loop, so ans = 5
When it comes to 花花酱‘'s version, it reduces one variable and simply returns . Actually, this is what binary search commonly does. So we can also reduce the variable of in Errichto's version and return . But why is the answer? Let me explain. I'll take the search range as .
Suppose we are in the last loop.
Then or , or there will be another loop.
Then .
There are obviously 2 situations: or .
If , then . Next we can prove .
if never changed(equals 0), then it means all the numbers in the array are . The first element in the array is obviously the smallest one satisfying .
if changed, no matter how many times it changed, the last time it changed must be in some loop that went this way:
... else // means nums[mid] < x L = mid + 1 // means mid == L - 1 ...
Are we seeing something here?
Yes, .
So , the th element is the smallest one satisfying .
If the search range is , we could also deduce the result.
Usage
The above is a very common usage of binary search, which is to find the lower bound of a value in a sorted array. There are some variant usages:
- find the upper bound, which is the smallest value satisfying . With a slight modification:
...
if (nums[mid] > X)
R = mid // or R = mid - 1 if search range [L, R]
...
- find the first occurance of the target. With adding some code after the while loop:
...
return (L < nums.length && nums[L] === X) ? L : -1;
- find the last occurance of the target. This can be solved by searching the first occurance of and then returning the previous one.
while (L <= R) {
mid = L + (R-L)/2;
if (nums[mid] >= X + 1)
R = mid - 1;
else
L = mid + 1;
}
return (L > 0 && nums[L-1] === X) ? L-1 : -1;