mirror of
https://github.com/labuladong/fucking-algorithm.git
synced 2025-07-05 03:36:39 +08:00
10
.github/ISSUE_TEMPLATE/others.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/others.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: 其他issue
|
||||
about: 我还有一些其他的建议/问题
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- 请清楚描述你的建议或问题 -->
|
14
.github/ISSUE_TEMPLATE/translate.md
vendored
14
.github/ISSUE_TEMPLATE/translate.md
vendored
@ -2,14 +2,16 @@
|
||||
name: 参与翻译
|
||||
about: 我想参与仓库中文章的翻译工作
|
||||
title: 'translate '
|
||||
labels: documentation
|
||||
labels: translate
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- 标题为 `translate` + 你要翻译的文章的`完整文件路径`,如 `translate 动态规划系列/抢房子.md`
|
||||
|
||||
内容一定要按照以下模板,根据你的具体内容进行修改
|
||||
内容一定要按照以下模板,根据你的具体内容进行修改。
|
||||
|
||||
若想翻译多篇文章,请开启多个 issue,不要挤在同一个 issue 中发布
|
||||
|
||||
以上为注释,不会显示在 issue 中。 -->
|
||||
|
||||
@ -20,4 +22,10 @@ assignees: ''
|
||||
|
||||
我准备将它翻译成:**英文**
|
||||
|
||||
**预计 X 天内翻译完成**,若由于种种原因没有完成,如果你愿意,你可以接替我的工作翻译这篇文章。
|
||||
**预计 X 天内翻译完成**,若由于种种原因,规定时间已过但此 issue 还未提交 pull request,则此 issue 自动失效。如果你愿意,你可以新开一个 issue 接替我的工作翻译这篇文章。
|
||||
|
||||
<!-- 理论上一篇文章只有几千字,所以最多允许占有 7 天,否则请把机会留给他人 -->
|
||||
|
||||
<!-- 如果由于某种原因你中途无法继续,请及时关闭你的 issue,把机会让给别人 -->
|
||||
|
||||
<!-- 如有你认为有特殊情况,时间必须大于 7 天,请说明原因 -->
|
BIN
算法思维系列/.DS_Store
vendored
Normal file
BIN
算法思维系列/.DS_Store
vendored
Normal file
Binary file not shown.
301
算法思维系列/BinarySearch.md
Normal file
301
算法思维系列/BinarySearch.md
Normal file
@ -0,0 +1,301 @@
|
||||
# Binary Search
|
||||
|
||||
Translator: [sinjoywong](https://blog.csdn.net/SinjoyWong)
|
||||
|
||||
Author: [labuladong](https://github.com/labuladong)
|
||||
|
||||
|
||||
Here is a joke:
|
||||
|
||||
One day Mr.Don went to library and borrowed N books. When he left the library, the alarm rang, so the security stopped Mr.Don to check if there are any book haven't been registered.
|
||||
|
||||
Mr.Don was about to check every book under the alertor, which was despised by the security, and he said: Don't you even know binary search? And the security split books in two parts, then put the first part under the alertor, it rang. So he split the first part books in to two parts again ..., finaly, after checked logN times, the security found the book which is not been registered, and he smiled sardonically. Then let Mr.Don took remainning books out of the library.
|
||||
|
||||
Since then, the library lost N - 1 books.
|
||||
|
||||
Is Binary search really a simple algorighm? Not really. Let's see what Knuth(the one who invented KMP algorithm) said:
|
||||
|
||||
> Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly trickey...
|
||||
|
||||
This article is going to discuss several the most commonly used binary search scenes: to find a number, to find its left boundary, to find its right boundary.
|
||||
And that we are going to discuss details, such as if inequality sign should with the equal sign, if mid should plus one, etc.
|
||||
After analysing the difference of these details and the reason why them come out, you can write binary search code flexibly and accuratly.
|
||||
|
||||
### Part zero: The Framework of Binary Search
|
||||
|
||||
```java
|
||||
int binarySearch(int[] nums,int target){
|
||||
int left = 0,right = ...;
|
||||
while(...){
|
||||
int mid = (right + left) / 2;
|
||||
if(nums[mid] == target){
|
||||
...
|
||||
}else if(nums[mid] < target){
|
||||
left = ...
|
||||
}else if(nums[mid] > target){
|
||||
right = ...
|
||||
}
|
||||
}
|
||||
return ...;
|
||||
}
|
||||
```
|
||||
|
||||
**A technique to analize binary search is: use `else if`, rather than using `else`, then we can manage all the details.**
|
||||
|
||||
In order to make it more simplier to understand, this article will use `else if` all along, you can optimize it after you truly understand it.
|
||||
|
||||
Hint: the `...` part is where we need focus to. When you implement binary search, pay attention to these parts firstly. We are going to analyze how it changes under sepecific circumastance.
|
||||
|
||||
Noted: when we calculate `mid`, we need to prevent it overflowing. You can see previous article, and here we assume you can handle it.
|
||||
|
||||
### 1. Find a number (Basic Binary Search)
|
||||
|
||||
This is the simpliest scene, we are going to search a number in a array. If it exists, return its index, otherwise return `-1`.
|
||||
|
||||
```java
|
||||
int binarySearch(int[] nums,int target){
|
||||
int left = 0;
|
||||
int right = nums.length - 1; //pay attention!
|
||||
|
||||
while(left <= right){
|
||||
int mid = (right + left) / 2;
|
||||
if(nums[mid] == target){
|
||||
return mid;
|
||||
}else if(nums[mid] < target){
|
||||
left = mid + 1;
|
||||
}else if(nums[mid] > target){
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
```
|
||||
|
||||
#### Q1.Why using `<=` in `while` loop rather than `<`?
|
||||
|
||||
>A1: Because when we initialize `right`, we set it to `nums.length - 1`, which is index of the last element, not `nums.length`.
|
||||
|
||||
Both of them may show up in different binary search implementions, here is diffenences: With the former, both ends are closed, like `[left,right]`, and the later is left open right close interval, like `[left,right)`, so when we use index `nums.length`, it will out of bounds.
|
||||
|
||||
We will use the former `[left,right]` implemention, which both ends are closed. **This is actually the interval we search every time**.
|
||||
|
||||
So when we should stop searching?
|
||||
|
||||
Of course we can stop when we find the target number in the array:
|
||||
|
||||
```java
|
||||
if(nums[mid] == target){
|
||||
return mid;
|
||||
}
|
||||
```
|
||||
|
||||
But if we havn't find it, we'll need to terminate `while` loop and return `-1`.
|
||||
So when we should terminal `while` loop? That's simple, **when the search inverval is empty, we should stop searching**, which means we have search all items and have nothing left, we just can't find target number in the array.
|
||||
|
||||
The terminal condition of `while(left <= right)` is `left == right + 1`, we can write it as inverval `[right + 1,right]`, or we can just put a specific number into it, like `[3,2]`. It's obvious that **the inverval is empty**, since there is no number which is larger than 3 and less-and-equal to 2. So we should terminate `while` loop and return -1;
|
||||
|
||||
The terminal condition of `while(wlft < right)` is `left == right`, we can write is as interval `[left,right]`, or we can also put a specific number into it, like `[2,2]`, **the interval is NOT empty**, there is still a number `2`, but the `while` loop is terminated, which means the interval `[2,2]` is missed, index 2 is not been searched, it's wrong when we return -1 directly.
|
||||
|
||||
It is allright if you want to use `while(left < right)` anyway. Since we know how the mistake occurred, we can fix it with a patch:
|
||||
|
||||
```java
|
||||
//...
|
||||
while(left < right){
|
||||
//...
|
||||
}
|
||||
return nums[left] == target ? left : -1;
|
||||
```
|
||||
|
||||
#### Q2: Why we implement it as `left = mid + 1`,`right = mid - 1`? I read others' code and they are implenting it as `right = mid` or `left = mid`, there is not so plus or minus, what's the difference?
|
||||
|
||||
>A2: This is also a difficulty of Binary Search implemention. But you can handle it if you can understand previous content.
|
||||
|
||||
We are aware of the concept of 'Search Interval' now, and in our implementation, the search intarval is both end closed, like `[left, right]`. So when we find index `mid` isn't the `target` we want, how to determine next search interval?
|
||||
|
||||
It is obviously that we will use `[left,mid - 1]` or `[mid + 1, right]`: we have just searched `mid`, so it should be removed from search interval.
|
||||
|
||||
#### Q3: What's the defects of this algorithm?
|
||||
|
||||
>A3: Since then, you should have already mastered all details of Binary Search, along with the reason why it works that way. However, there are some defects still.
|
||||
|
||||
For example, there is a sorted array `nums = [1,2,2,2,3]`, `targe = 2`, after processed with Binary Search Algorithm, we will get result `index = 2`. But if we want to get left boundary of `target`, which is `index = 1`, or if we want to get right boundary of `target`, which is `index = 3`, we cannot handle it with this algorithm.
|
||||
|
||||
It's a quite normal demand. Perhaps you would say, can't I find a target, then I search it from target to left(or right)? Sure you can, but it's not so good, since we cannt guarantee the time complexity with O(logn).
|
||||
|
||||
Here we will discuss this two kind of Binary Search Alghrithm.
|
||||
|
||||
### Part 2. Binary Search to look for left border
|
||||
|
||||
See codes below, and pay attention to marked details:
|
||||
|
||||
```java
|
||||
int left_bound(int[] nums,int target){
|
||||
if(nums.lengh == 0) return -1;
|
||||
int left = 0;
|
||||
int right = nums.length; // Attention!
|
||||
|
||||
while(left < right){ // Attention
|
||||
int mid = (left + right) / 2;
|
||||
if(nums[mid] == target){
|
||||
right = mid;
|
||||
}else if(nums[mid] < target){
|
||||
left = mid + 1;
|
||||
}else if(nums[mid] > target){
|
||||
right = mid; // Attention
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
```
|
||||
#### Q1: Why we use `while(left < right)`, rather than `<=`?
|
||||
|
||||
>A1: Analyze in the same way, since `right = nums.length` rather than `nums.length - 1`, the search interval is `[left, right)`, which is left closed right open.
|
||||
|
||||
>The terminal condition of `while(left < right)` is `left == right`. At this time search interval `[left,right)` is empty, so it can be terminated correctly.
|
||||
|
||||
#### Q2: Why there is no `return -1`? what if there is no `target` in `nums`?
|
||||
|
||||
>A2: Before this, let's think about what's meaning of `left border` is:
|
||||
|
||||

|
||||
|
||||
For this array, the algorithm will get result `1`. The result `1` can be interpreted this way: there is 1 element in `nums` which element is less than 2.
|
||||
|
||||
For example, a sorted array `nums = [2,3,5,7]`, `target = 1`, the alghrithm will return 0, which means there is 0 element in `nums` which element is less than 1.
|
||||
|
||||
For example, we have same sorted array as described above, and this time we have `target = 8`, the algorithm will get result `4`, which means there is 4 element in `nums` which element is less than `8`.
|
||||
|
||||
In summary, we can see the interval of return value using the alghrithm (which is the value of `left`) is closed interval `[0,nums.length]`, so we can simply add two line of codes to get `-1` result in proper time.
|
||||
|
||||
```java
|
||||
while(left < right){
|
||||
//...
|
||||
}
|
||||
//target is larger than all nums
|
||||
if(left == nums.length) return -1;
|
||||
//just like the way previously implenented
|
||||
return nums[left] == target ? left : -1;
|
||||
```
|
||||
|
||||
#### Q1: Why `left = mid + 1, right = mid`? It's kind of different with previous implement.
|
||||
|
||||
>A1: It's easy to explain. Since our search interval is [left,right), which is left closed right open, so when `nums[mid]` has been detected, in then next move, the search interval should remove `mid` and slit it to two intervals, which is `[left,mid)` and `[mid + 1, right)`.
|
||||
|
||||
#### Q4: Why this algorithm can be used for search left border?
|
||||
|
||||
>A4: The key is the solution when we meet `nums[mid] == target`:
|
||||
```java
|
||||
if (nums[mid] == target){
|
||||
right = mid;
|
||||
}
|
||||
```
|
||||
|
||||
>It's obviously that we don't return it immediatly when we find `target`, in the further we continuly search in interval `[left,mid)`, which is search towarding left and contract, then we can get left border.
|
||||
|
||||
#### Q5: Why return `left`, rather than `right`?
|
||||
|
||||
>A5: It's same way, because the terminal condition of `while` is `left == right`.
|
||||
|
||||
### Part Three: BINARY SEARCH TO FIND RIGHT BORDER
|
||||
|
||||
It's almost same with part two: binary search to find left border, there is only two differences, which is marked below:
|
||||
|
||||
```java
|
||||
int right_bound(int[] nums,int target){
|
||||
if(nums.length == 0) return -1;
|
||||
int left = 0, right = nums.length;
|
||||
|
||||
while(left < right){
|
||||
int mid = (left + right) / 2;
|
||||
if(nums[mid] == target){
|
||||
left = mid + 1; // Attention!
|
||||
}else if(nums[mid] < target){
|
||||
left = mid + 1;
|
||||
}else if(nums[mid] > target){
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return left - 1; //Attention!
|
||||
}
|
||||
```
|
||||
|
||||
#### Q1: Why this alghrithm can be used to find right border?
|
||||
|
||||
>A1: Similarly, key point is:
|
||||
|
||||
```java
|
||||
if(nums[mid] == target){
|
||||
left = mid + 1;
|
||||
}
|
||||
```
|
||||
|
||||
>When `nums[mid] == target`, we don't return immediately. On the contrary we enlarge the lower bound of search interval, to make serach interval move to right rapidlly, and finally we can get right border.
|
||||
|
||||
#### Q2: Why we return `left -1`, unlike when we process with left border algorithm and return `left`? In addition I think since we are searching right border, shouldn't we return `right` instead?
|
||||
|
||||
>A2: First of all, the terminal condition of `while` loop is `left == right`, so it's right to use both of them. You can return `right - 1` if you want to reflect `right`.
|
||||
|
||||
>As for why we should minus `1` here, it's a special point, let's see the condition judgement:
|
||||
|
||||
```java
|
||||
if(nums[mid] == target){
|
||||
left = mid + 1;
|
||||
//Thinking this way: mid = left - 1
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
When we update the value of `left`, we must do it this way: `left = mid + 1`, which means when `while` is terminated, `nums[left]` must not equal to `target`, but `nums[left-1]` could be equal to `target`.
|
||||
|
||||
As for why `left = mid + 1`, it's same as part two.
|
||||
|
||||
#### Q3: Why there is no `return -1`? what if there is no `target` in `nums`?
|
||||
|
||||
>A3: Like left border search, because the terminal condition of `while` is `left == right`, which means value interval of `left` is `[0,nums.length]`, so we can add some codes and `return -1` apprapoly:
|
||||
|
||||
```java
|
||||
while(left < right){
|
||||
// ...
|
||||
}
|
||||
if (lef == 0) return -1;
|
||||
return nums[left -1] == target ? (left -1) : -1;
|
||||
```
|
||||
|
||||
### Part Four: Summary
|
||||
|
||||
Let's tease out the causal logic of these detailed differences.
|
||||
|
||||
#### Firstly, we implement a basic binary search alghrithm:
|
||||
|
||||
Because we initialize `right = nums.length - 1`, it decided our search interval is `[left,right]`, and it also decided `left = mid + 1` and `right = mid - 1`.
|
||||
|
||||
Since we only need to find a index of `target`, so when `nums[mid] == target`, we can return immediately.
|
||||
|
||||
#### Secondly, we implement binary search to find left border:
|
||||
|
||||
Because we initialize `right = nums.length`, it decided our search interval is `[left,right)`, and it also decided `while (left < right)` ,and `left = mid + 1` and `right = mid`.
|
||||
|
||||
Since we need to find the left border, so when `nums[mid] == target`, we shouldn't return immediately, we need to tighten the right border to lock the left border.
|
||||
|
||||
#### Thirdly, we implement binary search to find right border:
|
||||
|
||||
Because we initialize `right = nums.length`, it decided our search interval is `[left,right)`,
|
||||
|
||||
it also decided `while(left < right)`,
|
||||
`left = mid + 1` and `right = mid`.
|
||||
|
||||
Since we need to find the left border, so when `nums[mid] == target`, we shouldn't return immediately, we need to tighten the left border to lock the right border.
|
||||
|
||||
For further consideration, we must set `left = mid + 1` when we tighten left border, so no matter we return `left` or `right`, we must `minus 1` with the result.
|
||||
|
||||
If you can understand all above, then congratulations, binary search alghrithm won't borther you any more!
|
||||
|
||||
According to this article, you will learn:
|
||||
|
||||
1. When we write binary search code, we don't use `else`, we will use `else if` instead to make our mind clear.
|
||||
|
||||
2. Pay attention to search interval and terminal condition of `while`. If there are any element missed, check it before we return the result.
|
||||
|
||||
3. If we need to search left/right border, we can get proper result when `nums[mid] == target`, and when we search right border, we should minus 1 to get result.
|
||||
|
||||
4. If we close both sides of border, we can only change the code in `nums[mid] == target` and return logic to get right answer. **Put it on your notes, it can be a template for binary search implementation!**
|
Reference in New Issue
Block a user