Skip to content

Commit 107229a

Browse files
authored
Merge pull request #53 from junnengsoo/branch-binarySearch
Branch binary search
2 parents e3d4b33 + b40e447 commit 107229a

File tree

6 files changed

+270
-19
lines changed

6 files changed

+270
-19
lines changed
71.1 KB
Loading
57.3 KB
Loading
66.4 KB
Loading
62.2 KB
Loading

src/main/java/algorithms/binarySearch/README.md

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,52 +10,100 @@ Two versions of binary search has been implemented in this repository - BinarySe
1010
Image Source: GeeksforGeeks
1111

1212
BinarySearch is a more straightforward and intuitive version of the binary search algorithm. In this approach, after the
13-
mid-value is calculated, the high and low pointers are adjusted by just one unit. From the above example, after mid
14-
points to index 4 in the first search, the low pointer moves to index 5 (+1) when narrowing the search. Similarly, when
15-
mid points to index 7 in the second search, the high pointer shifts to index 6 (-1) when narrowing the search. This
16-
prevents any possibility of infinite loops.
13+
mid-value is calculated, the high or low pointer is adjusted by just one unit. From the above example, after mid points
14+
to index 4 in the first search, the low pointer moves to index 5 (+1 from 4) when narrowing the search. Similarly, when
15+
mid points to index 7 in the second search, the high pointer shifts to index 6 (-1 from 7) when narrowing the search.
16+
This prevents any possibility of infinite loops. During the search, the moment mid-value is equal to the target value,
17+
the search ends prematurely. Note that there is no need for a "condition" method as the condition is already captured
18+
in the predicates of the if-else blocks.
1719

1820
## BinarySearchTemplated
1921

2022
BinarySearchTemplated removes the condition that checks if the current mid-value is equal to the target (which helps to
2123
end the search the moment the target is found). The template adds a "condition" method which will be modified based on
2224
the requirements of the implementation.
2325

24-
The narrowing of the search space differs from BinarySearch - only one of the high or low pointers will be adjusted by
25-
one unit.
26+
The narrowing of the search space differs from BinarySearch - only the high pointer will be adjusted by one unit.
2627

2728
This template will work for most binary search problems and will only require the following changes:
2829
- Search space (high and low)
2930
- Condition method
3031
- Returned value (low or low - 1)
3132

3233
### Search Space (Requires change)
33-
Simply modify the initialisation of the high and low pointer according to the search space.
34+
Simply modify the initialisation of the high and low pointer according to the [search space](#search-space-adjustment).
3435

3536
### Condition (Requires change)
3637
We assume that when the condition returns true, the current value "passes" and when the condition returns false, the
3738
current value "fails".
3839

39-
In this template, we want to find the first "pass" in the array.
40-
41-
INSERT IMAGE OF FIRST PASS
40+
Note that in this template, the conditional blocks
41+
```
42+
if (condition(x)) {
43+
high = mid;
44+
} else {
45+
low = mid + 1;
46+
}
47+
```
48+
requires elements that "fail" the condition to be on the left of the elements that "pass" the condition, see below, in a
49+
sorted array due to the way the high and low pointers are reassigned.
50+
51+
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated1.jpeg)
52+
53+
Hence, we will need to implement a condition method that is able to discern between arrays that "pass" and "fail"
54+
accurately and also place them in the correct relative positions i.e. "fail" on the left of "pass". Suppose we change
55+
the condition method implementation in BinarySearchTemplated from `value >= target` to `value <= target`, what will
56+
happen?
57+
<details>
58+
<summary> <b>what will happen?</b> </summary>
59+
The array becomes "P P F F F F" and the low and high pointers are now reassigned wrongly.
60+
</details>
4261

4362
### Returned Value (Requires change)
44-
In the implementation of BinarySearchTemplated, return low was used to find the first "pass".
63+
In this implementation of BinarySearchTemplated, we return the first "pass" in the array with `return low`. This is
64+
because our condition method implementation encompasses the target value that we are finding i.e. when
65+
`value == target`.
66+
67+
```java
68+
public static boolean condition(int value, int target) {
69+
return value >= target;
70+
}
71+
```
72+
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated2.jpeg)
73+
74+
However, if we want to return the last "fail" in the array, we will `return low - 1`.
75+
76+
Suppose now we modify the condition to be `value > target`, how can we modify our BinarySearchTemplated to still work as
77+
expected?
78+
<details>
79+
<summary> <b>value > target?</b> </summary>
80+
Replace `return low` with `return low - 1` and replace arr[low] with arr[low - 1] as now the target value is the last
81+
"fail".
82+
</details>
4583

46-
EXPLANATION TBC, STILL THINKING HOW TO PHRASE IT.
4784

4885
### Search Space Adjustment
49-
What should be the search space adjustment? (Why only low = mid + 1)
86+
What should be the search space adjustment? Why is only low reassigned with an increment and not high?
87+
88+
Due to the nature of floor division in Java's \ operator, if there are two mid-values within the search range, which is
89+
when the number of elements is even, the first mid-value will be selected. Suppose we do not increment the low pointer
90+
during reassignment, `low = mid`, let us take a look at the following example:
91+
92+
![binary search templated 1 img](../../../../../docs/assets/images/BinarySearchTemplated3.jpeg)
93+
94+
The search space has been narrowed down to the range of index 1 (low) to 2 (high). The mid-value is calculated,
95+
`mid = (1 + 2) / 2`, to be 1 due to floor division. Since `2 < 5`, we enter the else block where there is reassignment
96+
of `low = mid`. This means that the low pointer is still pointing to index 1 and the high pointer remains unchanged at
97+
index 2. This results in an infinite loop as the search range is not narrowed down.
5098

51-
Due to the nature of floor division in Java's \ operator, the searched mid-index will always be smaller than the high
52-
pointer of the previous search range. On the other hand, low = mid + 1, ensures that the searched mid-index is always
53-
larger than the low pointer of the previous search range. This ensures that the search range is narrowed in every loop
54-
and prevents the possibility of infinite loops.
99+
To resolve this issue, we need `low = mid + 1`, which will result in the low pointer pointing to index 2 in this
100+
scenario. We still ensure correctness because the mid-value is not the target value, as the mid-value < target, and we
101+
can safely exclude it from the search range.
55102

56-
INSERT IMAGE HERE TO EXPLAIN
103+
Why do we not need to increment the high pointer during reassignment? This is because the mid-value could be the target
104+
as the condition implemented is `value >= target`, hence, we cannot exclude it from the search range.
57105

58-
As we close in towards the target value, the final low = mid + 1 narrows the search range from low. TBC ON EXPLANATION
106+
See [here](./binarySearchTemplatedExamples/README.md) to use the template for other problems
59107

60108
Credits: [Powerful Ultimate Binary Search Template](https://leetcode.com/discuss/general-discussion/786126/python-powerful-ultimate-binary-search-template-solved-many-problems)
61109

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# BinarySearchTemplated Examples
2+
3+
We will be utilising the BinarySearchTemplated implementation to solve some problems.
4+
5+
The following will differ between problems:
6+
- Search Space
7+
- Condition
8+
- Returned Value
9+
10+
<b> How do we know if a problem can be solved with this template? </b>
11+
12+
A problem is well-suited for this approach when it meets the following criteria:
13+
14+
- <b> Clear Distinction Between Categories: </b> There should be a definitive way to categorize elements into two
15+
groups: "pass" and "fail".
16+
17+
- <b> Grouped Categories: </b> All the elements in one category should be grouped together without interspersion. This
18+
implies that if we move from a "fail" element to a "pass" one, all subsequent elements will be "pass".
19+
20+
- <b> Monotonic Property: </b> There needs to be a consistent, unidirectional trend or property in the search space.
21+
This means the condition being checked either consistently increases or decreases. For the classical binary search on a
22+
sorted array, this monotonic property is the sorted order of the elements, where the array elements increase
23+
(or decrease) consistently.
24+
25+
## 1. First Bad Version [Easy]
26+
27+
### Problem:
28+
29+
You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of
30+
your product fails the quality check. Since each version is developed based on the previous version, all the versions
31+
after a bad version are also bad.
32+
33+
Suppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the
34+
following ones to be bad.
35+
36+
You are given an API bool `isBadVersion(version)` which returns whether `version` is bad. Implement a function to find
37+
the first bad version. You should minimize the number of calls to the API.
38+
39+
Example 1:
40+
```
41+
Input: n = 5, bad = 4
42+
Output: 4
43+
Explanation:
44+
call isBadVersion(3) -> false
45+
call isBadVersion(5) -> true
46+
call isBadVersion(4) -> true
47+
Then 4 is the first bad version.
48+
```
49+
50+
### Solution:
51+
52+
<b> Search Space </b>
53+
54+
The search space includes all possible values.
55+
56+
```java
57+
high = n - 1;
58+
low = 0;
59+
```
60+
61+
<b> Condition </b>
62+
63+
The condition has already been implemented. But we need to check if the elements that pass the condition will be on the
64+
right of those that fail! Since "all the versions after a bad version are also bad", our template will work.
65+
66+
<b> Returned Value </b>
67+
68+
Since we want to return the first bad version, we will return low.
69+
70+
![first bad version img](../../../../../../docs/assets/images/firstBadVersion.jpeg)
71+
72+
<b> Full Solution: </b>
73+
74+
```java
75+
public int firstBadVersion(int n) {
76+
int high = n - 1; // max index for n elements
77+
int low = 0;
78+
while (low < high) {
79+
int mid = low + (high - low) / 2;
80+
if (isBadVersion(mid)) { // condition is isBadVersion
81+
high = mid;
82+
} else {
83+
low = mid + 1;
84+
}
85+
}
86+
87+
return low; // return first "pass"
88+
}
89+
```
90+
91+
## 2. Capacity To Ship Packages Within D Days [Medium]
92+
93+
### Problem:
94+
95+
A conveyor belt has packages that must be shipped from one port to another within `days` days.
96+
97+
The `ith` package on the conveyor belt has a weight of `weights[i]`. Each day, we load the ship with packages on the
98+
conveyor belt (in the order given by `weights`). We may not load more weight than the maximum weight capacity of the
99+
ship.
100+
101+
Return the least weight capacity of the ship that will result in all the packages on the conveyor belt being shipped
102+
within `days` days.
103+
104+
Example 1:
105+
```
106+
Input: weights = [1,2,3,4,5,6,7,8,9,10], days = 5
107+
Output: 15
108+
Explanation: A ship capacity of 15 is the minimum to ship all the packages in 5 days like this:
109+
1st day: 1, 2, 3, 4, 5
110+
2nd day: 6, 7
111+
3rd day: 8
112+
4th day: 9
113+
5th day: 10
114+
115+
Note that the cargo must be shipped in the order given, so using a ship of capacity 14 and splitting the packages into
116+
parts like (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) is not allowed.
117+
```
118+
119+
### Solution:
120+
121+
On first glance, while we are given an array, simply binary searching the array does not solve the problem. However,
122+
since our goal is to determine the least weight capacity of the ship, could this be our "target"? The answer is yes.
123+
By having each element being a least weight capacity case, with the lowest index being the heaviest weight and the
124+
highest index being the sum of all weights, we will have our search space. The monotonic property present is that if
125+
we can ship all packages within `D` days with capacity `m`, we can definitely ship them within `D` days with any
126+
capacity greater than `m`, such as `m + 1`.
127+
128+
<b> Search Space </b>
129+
130+
The search space is the range of least weight capacity we should search. The minimum least weight capacity that must be
131+
required is the heaviest package as we cannot split the heaviest package. The maximum least weight capacity that is
132+
necessary is the sum of all weights - i.e. we have to ship all packages within `1` day.
133+
134+
```java
135+
int sum = 0;
136+
for (i = 0; i < weights.length; i++) {
137+
sum += weights[i];
138+
}
139+
high = sum;
140+
low = weights[weights.length - 1];
141+
```
142+
143+
<b> Condition </b>
144+
145+
The condition is to check if it is feasible to ship all packages within `D` days with a least weight capacity of `m`.
146+
Where `D` is given and `m` is the mid-value in our search range.
147+
148+
```java
149+
public static boolean isFeasible(int[] weights, int days, int capacity) {
150+
int daysNeeded = 1;
151+
int currentWeight = 0;
152+
for (int i = 0; i < weights.length; i++) {
153+
if (currentWeight + weights[i] > capacity) {
154+
daysNeeded++;
155+
currentWeight = 0; // Reset current weight as we are starting a new day
156+
}
157+
currentWeight += weights[i];
158+
}
159+
return daysNeeded <= days; // If days needed is less than or equal to days, it is feasible
160+
}
161+
```
162+
163+
<b> Returned Value </b>
164+
165+
Since we want to return the minimum least weight capacity where we can deliver in time, we will return low.
166+
167+
<b> Full Solution: </b>
168+
169+
```java
170+
public int shipWithinDays(int[] weights, int days) {
171+
public boolean isFeasible(int[] weights, int days, int capacity) {
172+
int daysNeeded = 1;
173+
int currentWeight = 0;
174+
for (int i = 0; i < weights.length; i++) {
175+
if (currentWeight + weights[i] > capacity) {
176+
daysNeeded++;
177+
currentWeight = 0; // Reset current weight as we are starting a new day
178+
}
179+
currentWeight += weights[i];
180+
}
181+
return daysNeeded <= days;
182+
}
183+
184+
int sum = 0;
185+
for (i = 0; i < weights.length; i++) {
186+
sum += weights[i];
187+
}
188+
189+
high = sum;
190+
low = weights[weights.length - 1];
191+
192+
while (low < high) {
193+
int mid = low + (high - low) / 2;
194+
if (isFeasible(weights, days, mid)) { // condition is isFeasible
195+
high = mid;
196+
} else {
197+
low = mid + 1;
198+
}
199+
}
200+
201+
return low; // return first "pass"
202+
}
203+
```

0 commit comments

Comments
 (0)