Binary Search¶
Binary search problem characteristics¶
- Ordered binary search. You need to find an index or array element where ordering info is available, either explicitly (sorted array) or implicitly (partially sorted or other special info).
- monotony pattern. If the ordering info isn't available, but you can exclude "all" the possible cases from left or right by a condition comparing
f(mid)
to thetarget
.
Binary search problem solving techniques¶
- Clarify that you are trying to find the first one or find the last one.
- Clarify that you are trying to move the index or move the value (i.e. kth smallest number in a multiplicative table).
- Use an "ordering abstraction"
vless(target, f(mid))
. This ordering abstraction will produce a boolean array that indicate the ordering information between the target value and thef(mid)
. - Decide whether the left or right part the
f(mid)
should fall into. The principle to determine the predicate is simple: don't rule out the possible result (maintain the loop invariant). - Shrink the range accordingly based on the predicate decided in step 3.
- Test the case that the search range is small, such as only have one or two elements.
Binary search practical use case¶
- Find whether the given target is in the array.
- Find the position of the first value equal to the given target.
- Find the insertion position of the given target in the array.
- Find the position of the last value equal to the given target.
- Find the total number of x in a sorted array.
- Find the last element less than the target.
- Find the first element greater than the target.
Binary search in C++ STL¶
lower_bound
: return iterator point to the element no less than the target.upper_bound
: return iterator point to the element greater than the target.equal_range
: return a pair of iterators, the first of which islower_bound
, the second isupper_bound
.binary_search
: return true if an element equivalent toval
is found, and false otherwise.
Caveat of binary search implementation¶
- Specify the range:
[start, end)
or[start, end]
? C++ STL used[start, end)
to denote a range, which bring in many conveniences. We will stick on this convention. - Which while loop condition?
start < end
?start <= end
?start != end
?start + 1 < end
? - The calculation of the
mid
.mid = start + (end - start) / 2
ormid = (start + end) / 2
? - To proof
mid
is always in the range[begin, end)
. - The "bisection":
start = mid + 1
,start = mid
, orend = mid - 1
orend = mid
? - Where is the result?
start
?end
? How to make sure?
A "universal" binary search implementation¶
Despite the above caveats, just remember that there are two versions of binary search one can write based on the range [begin, end)
and [begin, end]
. Iterator type in C++ using the former, it have many benefits in reduce the code complexity. Among all the binary search implementation you have seen, the following one is the most powerful version and it equivalent to C++ STL lower_bound
algorithm.
/**
* return an index to an element no less than x. Be more specifically, if there is
* an element in the given array equal to x, it returns the index of first such
* element; if there is no element that is equal to x, it returns the index
* where x can be inserted into the position without changing the ordering of
* the elements.
*
* All possible return value for calling this function with array.size() == n is
* [0, 1, ..., n - 1, n]
*
*/
size_t binary_search(int x, vector<int>& array, size_t n)
{
size_t begin = 0, end = n;
while (begin != end) {
size_t mid = begin + (end - begin) / 2;
if (array[mid] < x) {
begin = mid + 1;
} else {
end = mid;
}
}
return begin;
}
Important observations about this implementation¶
-
mid
cannot less thanbegin
, they can be equal. This will ensurebegin = mid + 1
in the if statement at least reduce the size of [begin, end] by 1.- Informal proof:
if (array[mid] < x)
, it indicate x can only possible be in array[mid + 1, mid + 2, ... n - 1].mid + 1
is at least 1 greater than begin.
- Informal proof:
-
mid
andend
never equal inside the while loop,mid < end
is always hold. This will ensureend = mid
in the else statement at least reduce the size of[begin, end]
by 1.- Informal proof: we have
begain < end
, sobegin + end < 2 * end
, thus(begin + end) / 2 < end
, because integer divisioin truncate down,mid = (begin + end) / 2
always less than end.
- Informal proof: we have
-
begin
andend
never cross.- Informal proof: Inside the while loop, at the begining, we have
begin < end
. If the current iteration executes theif
condition,begin = mid + 1
at most advancebegin
toend
but not exceedend
. If it execute theelse
condition,end = mid
would at worst case changeend
point to the minimum value ofmid
, because we havebegin <= mid
. Thus, we can conclude that executing the statementend = mid
will not changeend
less thanbegin
, at worst equal tobegin
.
- Informal proof: Inside the while loop, at the begining, we have
Claims regarding this binary search routine¶
- The range
[begin, end)
is used, which comply to the convention used in C++ iterator. - It is impossible that
mid == end
. If they are equal,array[n]
is invalid memory access. - We use the loop condition
while (begin != end)
to indicate that once the loop terminates, we havebegin == end
. By checking whetherbegin
is a valid index to the array or not, we can know whetherx
is greater than all the elements in the array or not. If we want to check whetherx
is found in the array, we simply checkarray[begin] == x
. However, this condition is based on the assumption thatbegin < end
initially. Considering that, usingwhile (begin < end)
is better if you cannot ensurebegin < end
before the loop. - Setting
begin = mid + 1
reduces the size of the remaining interested sub-array and maintains the invariant, which is if x in the array, x is in[begin, end)
. - Setting
end = mid
reduces the size of the remaining interested sub-array (mid never equal to end) and maintains the invariant, which is if x in the array, x is in[begin, end)
. This claim is a little hard to absorb. On way to understand is like the following: ~~Because we need keep searching x in the range[begin, mid]
if we get in the else statement. In the else case there are two possibilities: 1)array[mid] > x
. 2)array[mid] = x
. For 1) it indicatesx
is in[begin, mid)
, settingend = mid
maintains the loop invariant correctly, which is thatx
is in the shrinked range. For the 2) it is a little complex. Ifarray[mid]
is the only element equal to x, settingend = mid
appears violate the loop invariant by exclude x from the range[begin, end)
. however, rememberarray[mid]
is the only element equal to x, after the while loop,begin = end
, we have thex
found bybegin
even though theoretically[begin, end)
is already an empty range sincebegin = end
andarray[begin] = array[end] = x
. If there are more values are equal tox
before and after the elementarray[mid]
the loop will always end up finding the firstx
value in the array. - If we use
end = mid + 1
. Try test case[1, 3, 5, 7]
, withx = 0
. deadloop will accur. i.e.begin = 0, mid = 1, end = 2
.
Category 1 Binary search on sorted arrays¶
To solve this type of binary search problem. You should focus on the following:
- Come up test cases to verify your solution.
- Be able to find which side to drop for each iteration.
- Be extremly careful "off by 1" bugs. (1. reasoning: is mid value possible to be the solution or not. 2. exercise test cases: especially short ones)
34. Search for a Range¶
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> res(2, -1);
int low = lower_bound(nums.begin(), nums.end(), target) - nums.begin();
int high = upper_bound(nums.begin(), nums.end(), target) - nums.begin();
if (low == high)
return res;
return {low, hight - 1};
}
};
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> res(2, -1);
int low = lower_bound(nums, target);
//int high = lower_bound(nums, target + 1); // also works.
int high = upper_bound(nums, target);
if (low == high) {
return res;
}
return {low, high - 1};
}
int lower_bound(vector<int>& nums, int target) {
if (nums.size() == 0) return 0;
int l = 0, r = nums.size();
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] < target) {
l = m + 1;
} else {
r = m;
}
}
return l;
}
int upper_bound(vector<int>& nums, int target) {
if (nums.size() == 0) return 0;
int l = 0, r = nums.size();
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] <= target) {
l = m + 1;
} else {
r = m;
}
}
return l;
}
};
class Solution(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
if len(nums) == 0:
return [-1, -1]
begin = 0
end = len(nums)
while begin != end:
mid = begin + (end - begin) / 2
if nums[mid] < target:
begin = mid + 1
else:
end = mid
if begin == len(nums):
return [-1, -1]
if nums[begin] == target:
lower = begin
else:
lower = -1
begin = 0
end = len(nums)
while begin != end:
mid = begin + (end - begin) / 2
if nums[mid] <= target:
begin = mid + 1
else:
end = mid
if nums[begin - 1] == target:
upper = begin - 1
else:
upper = -1
return [lower, upper]
35. Search Insert Position¶
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if (nums.size() == 0) return 0;
int l = 0, r = nums.size();
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] < target) {
l = m + 1;
} else {
r = m;
}
}
return l;
}
};
33. Search in Rotated Sorted Array¶
How to locate the sorted half?
- If left half is sorted, check where the target
t
is like be. else if right half is sorted, check where the targett
is like to be. else if mid element is equal to left or right. Remove one of them. - Although no duplicate, should consider short input like
[3 1], 1
will have the equal case.
/**
t = 1 t = 3 t = 5 t = 4 t = -1
5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4
5 1 3 4 5 1 3 4 5 1
1 3 5 4 1 <--need check
*/
class Solution {
public:
int search(vector<int>& A, int t) {
if (A.empty()) return -1;
int l = 0, r = A.size() - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (A[m] == t) return m;
if (A[l] < A[m]) { // left is sorted
if (A[l] <= t && t < A[m]) {
r = m - 1;
} else {
l = m + 1;
}
} else if (A[m] < A[r]) { // right is sorted
if (A[m] < t && t <= A[r]) {
l = m + 1;
} else {
r = m - 1;
}
} else { // if equal, remove one. case: [3, 1], 1
if (A[l] == A[m]) l++;
if (A[m] == A[r]) r--;
}
}
return A[l] == t ? l : -1;
}
};
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if len(nums) == 0:
return -1
left = 0
right = len(nums) - 1
while left < right:
mid = left + (right - left) / 2
if nums[mid] == target:
return mid;
if nums[left] < nums[mid]:
if nums[left] <= target and target < nums[mid]:
right = mid - 1
else:
left = mid + 1
elif nums[mid] < nums[right]:
if nums[mid] < target and target <= nums[right]:
left = mid + 1
else:
right = mid - 1
else:
if nums[left] == nums[mid]:
left += 1
if nums[right] == nums[mid]:
right -= 1
if nums[left] == target:
return left
return -1
81. Search in Rotated Sorted Array II¶
How to locate the sorted half?
class Solution {
public:
bool search(vector<int>& A, int t) {
if (A.empty())
return false;
int l = 0, r = A.size() - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (A[m] == t) return true;
if (A[l] < A[m]) {
if (A[l] <= t && t < A[m]) {
r = m - 1;
} else {
l = m + 1;
}
} else if (A[m] < A[r]) {
if (A[m] < t && t <= A[r]) {
l = m + 1;
} else {
r = m - 1;
}
} else {
if (A[l] == A[m]) l++;
if (A[m] == A[r]) r--;
}
}
return A[l] == t? true : false;
}
};
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if len(nums) == 0:
return False
left = 0
right = len(nums) - 1
while left < right:
mid = left + (right - left) / 2
if nums[mid] == target:
return True
if nums[left] < nums[mid]:
if nums[left] <= target and target < nums[mid]:
right = mid - 1
else:
left = mid + 1
elif nums[mid] < nums[right]:
if nums[mid] < target and target <= nums[right]:
left = mid + 1
else:
right = mid - 1
else:
if nums[left] == nums[mid]:
left += 1
if nums[right] == nums[mid]:
right -= 1
if nums[left] == target:
return True
return False
153. Find Minimum in Rotated Sorted Array¶
Try to locate the valley which contains the min.
- Notice when
A[0] < A[n - 1]
, returnA[0]
. - Draw a monotonic curve and then split the curve into two half, swith the order. This can help you to write the code.
class Solution {
public:
int findMin(vector<int>& A) {
int l = 0, r = A.size() - 1;
while (l < r) {
if (A[l] < A[r]) // serve as base case.
return A[l];
int m = l + (r - l) / 2;
if (A[m] > A[r]) { // also works. looking for not sorted half
l = m + 1;
} else if (A[m] < A[r]) { // don't really need if statement
r = m;
}
}
return A[l];
}
};
class Solution(object):
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return -1
left = 0
right = len(nums) - 1
while left < right:
if nums[left] == nums[right]:
return nums[left]
mid = left + (right - left) / 2
if nums[mid] > nums[right]:
left = mid + 1
else:
right = mid
return nums[left]
154. Find Minimum in Rotated Sorted Array II¶
Locate the valley which contains the min.
- Since duplicates exist. we cannot use the observation
A[l] == A[r]
. - Here we deal with duplicates using decrease by one step.
class Solution {
public:
int findMin(vector<int>& A) {
int l = 0, r = A.size() - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (A[m] > A[r]) {
l = m + 1;
} else if (A[m] < A[r]) {
r = m;
} else {
r--;
}
}
return A[l];
}
};
class Solution(object):
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if len(nums) == 0:
return -1
left = 0
right = len(nums) - 1
while left < right:
mid = left + (right - left) / 2
if nums[mid] > nums[right]:
left = mid + 1
elif nums[mid] < nums[right]:
right = mid
else:
right -= 1
return nums[left]
162 Find Peak Element¶
Use Binary search
- Use the neighboring relation to determin which side a peak value may occur then eliminate the other side.
class Solution {
public:
int findPeakElement(vector<int>& A) {
int l = 0, r = A.size() - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (A[m] < A[m + 1]) {
l = m + 1;
} else if (A[m] > A[m + 1]) {
r = m;
}
}
return l;
}
};
278. First Bad Version¶
Binary search
- Notice how this can be related to the ordering abstraction.
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int l = 0, r = n;
while (l < r) {
int m = l + (r - l) / 2;
if (!isBadVersion(m)) {
l = m + 1;
} else {
r = m;
}
}
return l;
}
};
74. Search a 2D Matrix¶
Binary search
- We can view the matrix as a big sorted array and then binary search the target.
- Notice test your finished routine using edge cases. (i.e. the initial value of end)
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
if (m == 0 || n == 0) return false;
int start = 0, end = m * n - 1;
while (start < end) {
int mid = start + (end - start) / 2;
int i = mid / n, j = mid % n;
if (matrix[i][j] < target) {
start = mid + 1;
} else {
end = mid;
}
}
return matrix[start / n][start % n] == target ? true : false;
}
};
240. Search a 2D Matrix II¶
Binary search to exclude whole column or whole row
- the key is you decide where to start the compare. If you start from left bottom or right top, the solution should be abvious.
- Notice the idea is from binary search, if ordering info available, we want to exclude as many as impossible values as we can.
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
if (m == 0 || n == 0) return false;
int x = m - 1, y = 0;
while (x >= 0 && y < n) {
if (matrix[x][y] == target) {
return true;
}
if (matrix[x][y] < target) {
y++;
} else {
x--;
}
}
return false;
}
};
302. Smallest Rectangle Enclosing Black Pixels¶
class Solution {
public:
int minArea(vector<vector<char>>& image, int x, int y) {
int m = image.size();
int n = m ? image[0].size() : 0;
int top = m, bottom = 0, left = n, right = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (image[i][j] == '1') {
top = min(top, i);
bottom = max(bottom, i + 1);
left = min(left, j);
right = max(right, j + 1);
}
}
}
return (right - left) * (bottom - top);
}
};
Binary search
- Notice the binary search idea is related to the problem Smallest Good Base and Wood Cut.
- The basic idea is to search each of
1
from 4 directions. First, make sure you can search one boundary and the others are similar. For example, to search the first row that contains1
, we can look at the whole column/row to see whether this col/row have1
. Because we are searching the first row that have1
top down, bisec based on the count of1
on each row we can know whether we ignore the upper half or the lower half.
class Solution {
public:
int minArea(vector<vector<char>>& image, int x, int y) {
int m = image.size();
int n = m ? image[0].size() : 0;
int top = bsearch_byrows(image, 0, x, 0, n, true); // search top
int bottom = bsearch_byrows(image, x + 1, m, 0, n, false);
int left = bsearch_bycols(image, 0, y, top, bottom, true);
int right = bsearch_bycols(image, y + 1, n, top, bottom, false);
return (bottom - top) * (right - left);
}
int bsearch_byrows(vector<vector<char>>& image, int x, int y,
int left, int right, bool white2black) {
while (x < y) {
int m = (x + y) / 2;
int k = left;
while (k < right && image[m][k] == '0') k++;
if (k < right == white2black) { // mth row have '1'
y = m;
} else {
x = m + 1;
}
}
return x;
}
int bsearch_bycols(vector<vector<char>>& image, int x, int y,
int top, int bottom, bool white2black) {
while (x < y) {
int m = (x + y) / 2;
int k = top;
while (k < bottom && image[k][m] == '0') k++;
if (k < bottom == white2black) { // mth column have '1'
y = m;
} else {
x = m + 1;
}
}
return x;
}
};
class Solution(object):
def minArea(self, image, x, y):
"""
:type image: List[List[str]]
:type x: int
:type y: int
:rtype: int
"""
m = len(image)
n = 0
if m != 0:
n = len(image[0])
top = self.bsearch_row(image, 0, x, 0, n, True)
bottom = self.bsearch_row(image, x + 1, m, 0, n, False)
left = self.bsearch_col(image, 0, y, top, bottom, True)
right = self.bsearch_col(image, y + 1, n, top, bottom, False)
return (bottom - top) * (right - left)
def bsearch_row(self, image, start, end, lower, upper, white2black):
while start < end:
m = (start + end) / 2
k = lower
while k < upper and image[m][k] == '0':
k += 1
if (k < upper) == white2black:
end = m
else:
start = m + 1
return start
def bsearch_col(self, image, start, end, lower, upper, white2black):
while start < end:
m = (start + end) / 2
k = lower
while k < upper and image[k][m] == '0':
k += 1
if (k < upper) == white2black:
end = m
else:
start = m + 1
return start
class Solution {
public:
int minArea(vector<vector<char>>& image, int x, int y) {
int m = image.size();
int n = m ? image[0].size() : 0;
int top = m, bottom = 0, left = n, right = 0;
int xx[4] = {-1, 0, 1, 0};
int yy[4] = {0, 1, 0, -1};
queue<pair<int, int>> q;
q.push({x, y});
image[x][y] = '0';
while (!q.empty()) {
pair<int, int> t = q.front(); q.pop();
top = min(top, t.first);
bottom = max(bottom, t.first + 1);
left = min(left, t.second);
right = max(right, t.second + 1);
for (int k = 0; k < 4; ++k) {
int a = t.first + xx[k];
int b = t.second + yy[k];
if (a >= 0 && a < m && b >= 0 && b < n && image[a][b] == '1') {
q.push({a, b});
image[a][b] = '0';
}
}
}
return (right - left) * (bottom - top);
}
};
from collections import deque
class Solution(object):
def minArea(self, image, x, y):
"""
:type image: List[List[str]]
:type x: int
:type y: int
:rtype: int
"""
m = len(image)
n = 0
if m != 0:
n = len(image[0])
xx = [-1, 0, 1, 0]
yy = [0, -1, 0, 1]
top = m
bottom = 0
left = n
right = 0
q = deque()
q.append([x, y])
image[x][y] = '0'
while len(q) > 0:
t = q.popleft()
top = min(top, t[0])
bottom = max(bottom, t[0] + 1)
left = min(left, t[1])
right = max(right, t[1] + 1)
for k in range(4):
a = t[0] + xx[k]
b = t[1] + yy[k]
if a >= 0 and a < m and b >= 0 and b < n and image[a][b] == '1':
q.append([a, b])
image[a][b] = '0'
return (right - left) * (bottom - top)
class Solution {
private:
int m, n;
int top, bottom, left, right;
public:
int minArea(vector<vector<char>>& image, int x, int y) {
m = image.size();
n = m ? image[0].size() : 0;
top = m, bottom = 0, left = n, right = 0;
dfs_helper(image, x, y);
return (right - left) * (bottom - top);
}
void dfs_helper(vector<vector<char>>& image, int x, int y) {
if (x < 0 || x >= m || y < 0 || y >= n || image[x][y] == '0') {
return;
}
image[x][y] = '0';
top = min(top, x);
bottom = max(bottom, x + 1);
left = min(left, y);
right = max(right, y + 1);
dfs_helper(image, x - 1, y);
dfs_helper(image, x, y + 1);
dfs_helper(image, x + 1, y);
dfs_helper(image, x, y - 1);
}
};
363. Max Sum of Rectangle No Larger Than K¶
Iterate the wide of the matrix and using prefix sum and set lower_bound
.
- From the problem Max Sum of Subarry No Larger Than K, we have to enumerate the width of the sub-matrix and sum up all row elements and get an array of length
m
,m
is the number of rows of the matrix. Then apply the method.
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
if (matrix.empty()) return 0;
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
int res = INT_MIN;
for (int l = 0; l < n; ++l) {
vector<int> sums(m, 0);
for (int r = l; r < n; ++r) {
for (int i = 0; i < m; ++i) {
sums[i] += matrix[i][r];
}
set<int> preSumSet;
preSumSet.insert(0);
int preSum = 0, curMax = INT_MIN;
for (int sum : sums) {
preSum += sum;
set<int>::iterator it = preSumSet.lower_bound(preSum - k);
if (it != preSumSet.end()) {
curMax = max(curMax, preSum - *it);
}
preSumSet.insert(preSum);
}
res = max(res, curMax);
}
}
return res;
}
};
merge sort
- The idea is similar that solution 1. Instead of calculate
preSum
on the fly, we finish calculation and pass it to amergeSort
routine. - The use
mergeSort
here is to find theA[j] - A[i] <= k
efficiently,O(nlogn)
.
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
int res = INT_MIN;
vector<long long> sums(m + 1, 0);
for (int l = 0; l < n; ++l) {
vector<long long>sumInRow(m, 0);
for (int r = l; r < n; ++r) {
for (int i = 0; i < m; ++i) {
sumInRow[i] += matrix[i][r];
sums[i + 1] = sums[i] + sumInRow[i];
}
res = max(res, mergeSort(sums, 0, m + 1, k));
if (res == k) return k;
}
}
return res;
}
int mergeSort(vector<long long>& sums, int start, int end, int k) {
if (end == start + 1) return INT_MIN;
int mid = start + (end - start) / 2;
int res = mergeSort(sums, start, mid, k);
if (res == k) return k;
res = max(res, mergeSort(sums, mid, end, k));
if (res == k) return k;
long long cache[end - start];
int j = mid, c = 0, t = mid;
for (int i = start; i < mid; ++i) {
while (j < end && sums[j] - sums[i] <= k) ++j; // search first time sums[j] - sums[i] > k
if (j - 1 >= mid) { // sums[j - 1] - sums[i] <= k, make sure j - 1 is still in right side
res = max(res, (int)(sums[j - 1] - sums[i]));
if (res == k) return k;
}
while (t < end && sums[t] < sums[i]) {
cache[c++] = sums[t++];
}
cache[c++] = sums[i];
}
for (int i = start; i < t; ++i) {
sums[i] = cache[i - start];
}
return res;
}
};
540. Single Element in a Sorted Array¶
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
start = 0
end = len(nums) - 1
while start < end:
mid = start + (end - start) // 2
if mid % 2 == 0:
if nums[mid] == nums[mid + 1]:
start = mid + 2
else:
end = mid
else:
if nums[mid] == nums[mid - 1]:
start = mid + 1
else:
end = mid
return nums[start]
Category 2 Using ordering abstraction¶
69. Sqrt(x)¶
Solution 1 using ordering abstraction definition
To find a square root of a integer x
using binary search. We need to first determin the range [left, right] that the target value sqrt(x)
may in. The potential range we can search is [0, x/2 + 1]
.
Then we should clarify this binary search is the "find the first one" type or the "find the last one" type. Basically, we want to determine our ordering abstraction f(target, g(i))
that is able to produce a boolean array. The boolean array have true part and false part seperated. Here target = sqrt(x)
and g(i) = i
. We define f(sqrt(x), i) = true
when i <= sqrt(x)
and f(sqrt(x), i) = false
when i > sqrt(x)
. This came from the following intuition: We are looking for the "last" integer whose square is less than x
. Why not the otherwise? Because if you change to find the "first" integer whose square is greater than the x
from right section of the boolean array, it is hard to define our ordering abstraction f
. Of cause, we can search the "first" integer whose square is greater than x
and find the previous integer next to it as the solution, but this later solution is a bit complex and counter intuitive. We prefer the first definition of ordering abstraction. Although a workable solution following the second ordering abstraction is also given below.
For example: to solve the sqrt(8) and sqrt(9) using our definition:
k, i = 0 1 2 3 4 5 6 7 8 9 10 n = 11
A = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
f(sqrt(8), k) = [T T T F F]
f(sqrt(9), k) = [T T T T F]
The binary search routine will be:
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x / 2 + 1;
while (l < r) {
// int m = l + (r - l) / 2; // will deadloop for 4, why?
int m = r - (r - l) / 2;
if (m <= x / m) {
l = m;
} else {
r = m - 1;
}
}
return l;
}
};
Solution 2 using the alternative ordering abstraction definition
Second ordering abstraction (find first value whose square is greater than x)
k, i = 0 1 2 3 4 5 6 7 8 9 10 n = 11
A = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
f(sqrt(8), k) = [F F F T T]
f(sqrt(9), k) = [F F F F T]
class Solution {
public:
int mySqrt(int x) {
if (x == 0) return 0; // should handle, but will got division by zero in line 9.
int l = 0, r = x / 2 + 2; // r = x / 2 + 1 will not working for x = 1, have to have the one past last;
while (l < r) {
//int m = r - (r - l) / 2; // will dead loop for 4
int m = l + (r - l) / 2;
if (m > x / m) {
r = m;
} else {
l = m + 1;
}
}
return l - 1;
}
};
367. Valid Perfect Square¶
Solution 1 Binary search using ordering abstraction
- Notice you have to run tests for cases from 1 to 5.
class Solution {
public:
bool isPerfectSquare(int num) {
if (num == 1) return true;
int begin = 1, end = num / 2;
while (begin < end) {
//long long mid = begin + (end - begin) / 2; // not working, deadloop for 5
long long mid = end - (end - begin) / 2;
if (mid * mid == num)
return true;
if (mid * mid < num) {
begin = mid;
} else {
end = mid - 1;
}
}
return false;
}
};
class Solution(object):
def isPerfectSquare(self, num):
"""
:type num: int
:rtype: bool
"""
if num == 1:
return True
lower = 1
upper = num / 2
while lower < upper:
mid = upper - (upper - lower) / 2
if mid * mid == num:
return True
if mid * mid < num:
lower = mid
else:
upper = mid - 1
return False
441. Arranging Coins¶
Solution 1 Biinary search
- Notice the integer overflow possibility, we use
long
to deal with it.
class Solution {
public:
int arrangeCoins(int n) {
if (n < 2) {
return n;
}
long l = 0;
long r = n;
while (l < r) {
long m = l + (r - l) / 2;
long t = m * (m + 1) / 2;
if (t == n)
return m;
if (t < n) {
l = m + 1;
} else {
r = m;
}
}
return l - 1;
}
};
633. Sum of Square Numbers¶
Solution 1 Binary search
- Once you have derived value
b
froma
andc
, you can binary searchb
.
class Solution {
public:
bool judgeSquareSum(int c) {
if (c == 0) return true;
for (long long a = 0; a * a <= c; ++a) {
int b = c - (int) (a * a);
int l = 0, r = b / 2 + 1;
while (l < r) {
long long m = r - (r - l) / 2;
if (m * m == b)
return true;
if (m * m < b) {
l = m;
} else {
r = m - 1;
}
}
}
return false;
}
};
Solution 2 Two pointers
- Notice this square sum can be found efficiently using two pointers.
class Solution {
public:
bool judgeSquareSum(int c) {
int a = 0, b = sqrt(c);
while(a <= b){
int sum = a * a + b * b;
if(sum < c) a++;
else if(sum > c) b--;
else return true;
}
return false;
}
};
Solution 3 Using set
- Keep inserting the value into a set, in the meantime also look up the other
class Solution {
public:
bool judgeSquareSum(int c) {
set<int> s;
for (int i = 0; i <= sqrt(c); ++i) {
s.insert(c - i*i);
if (s.count(i*i)) return true;
}
return false;
}
};
658. Find K Closest Elements¶
Solution 1 Binary search
- Compare to problem 475. Heaters
- Our search target is to find the starting index of the subarray of length K.
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int start = 0, end = arr.size() - k;
while (start < end) {
int mid = start + (end - start) / 2;
// looking for a "mid" that
if (x - arr[mid] > arr[mid + k] - x) {
start = mid + 1;
} else {
end = mid;
}
}
return vector<int>(arr.begin() + start, arr.begin() + start + k);
}
};
Solution 2 Binary search and Two pointers
- We first use binary search to locate the x value then expand to left and right looking for the k closest elements
- Notice the
i < 0
in the if condition, it is very important to be there. otherwise the array index will be out of bound.
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int index = lower_bound(arr.begin(), arr.end(), x) - arr.begin();
int i = index - 1, j = index;
while (k--) {
if (i < 0 || j < arr.size() && abs(arr[j] - x) < abs(arr[i] - x)) {
j++;
} else {
i--;
}
}
return vector<int>(arr.begin() + i + 1, arr.begin() + j);
}
};
611. Valid Triangle Number¶
- The main idea comes from the triangle lateral property, in which the triple should fullfil:
a + b > c
,a + c > b
, andb + c > a
. Once we sort it. We are able to gain some advantages that we don't have to check all these 3 relations. Instead, we should only take care ofA[i] + A[j] > A[k]
, in whichi < j < k
. - Because we sorted the array, we can also fix the
i
andj
, using binary search to find thek
in the ragne ofA[j + 1] ~ A[n - 1]
. We can use our classic binary search template to achieve the goal.
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int n = nums.size();
int res = 0;
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 2; ++i) {
for (int j = i + 1; j < n - 1; ++j) {
int l = j + 1, r = n; // range of all possible k, notice l start with j + 1
int t = nums[i] + nums[j];
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] < t) {
l = m + 1;
} else {
r = m;
}
}
res += l - j - 1; // notice the count start from j + 1 to l - 1.
}
}
return res;
}
};
Category 3 Using ordering abstration (monotonicity)¶
287. Find the Duplicate Number¶
Solution 1 Binary search
- The problem asking for better than
O(n^2)
we could check to see whether binary search will work. - If you count how many values
<=
the mid elements of[1, ..., n-1]
, it will give you enough information to discard part of the array. - Here you should distinguish what will be split and what will be searched. The answer is the
[1, ..., n-1]
sequence, not the given array. The simple proof of why it works can be put in this the following way. - If the count of elements that
<=mid
in the array is less thanmid
, we can learn that the duplicate is in the higher end. If the count is greater, we can know that the duplicate element is in the lower end of the sequence[1, ..., n-1]
.
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int begin = 1, end = nums.size() - 1;
while (begin < end) {
int mid = begin + (end - begin) / 2;
int count = 0;
for (int a : nums) {
if (a <= mid) ++count;
}
if (count <= mid) // "=" for [1,2,2]
begin = mid + 1;
else
end = mid;
}
return begin;
}
};
Solution 2 tortoise and hare algorithm
- This problem is very similar to the the find circle in linked list. Generally, if you repeate
A[A[i]]
, the out put will show some periodic patterns. In fact you can imagine a rho shaped sequence. - Image there is a function
f(i) = A[i]
, it mapping from1, 2, 3, ... n
to1, 2, 3, ... n
. Try to traverseA[i]
, you will finally get circle through some same sequence of elements again and again, thus you obtain a rho shaped sequency like a circle in a linked list. The reason of it being a rho shape is becuase at least one element you will not come back to it if you leave it. - Find Duplicate
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
int slow = 0, fast = 0, find = 0;
while(slow != fast || (slow == 0 && fast == 0)) {
slow = nums[slow];
fast = nums[nums[fast]];
}
while (slow != find) {
slow = nums[slow];
find = nums[find];
}
return find;
}
};
360. Sort Transformed Array¶
374. Guess Number Higher or Lower¶
// Forward declaration of guess API.
// @param num, your guess
// @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
int guess(int num);
class Solution {
public:
int guessNumber(int n) {
int start = 1, end = n;
while(start < end) {
int mid = start + (end - start) / 2;
if (guess(mid) == 0)
return mid;
if (guess(mid) == 1) {
start = mid + 1;
} else {
end = mid;
}
}
return start;
}
};
# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num):
class Solution(object):
def guessNumber(self, n):
"""
:type n: int
:rtype: int
"""
begin = 0
end = n
while begin != end:
mid = begin + (end - begin) / 2
if guess(mid) == 0:
return mid
if guess(mid) == 1:
begin = mid + 1
else:
end = mid
return begin
475. Heaters¶
Sort then brute force
- The solution we are looking for is the max value of the smallest house-heater distance.
- Think through what is the distance you want to keep, min or max
class Solution {
public:
int findRadius(vector<int>& houses, vector<int>& heaters) {
int m = houses.size();
int n = heaters.size();
sort(houses.begin(), houses.end());
sort(heaters.begin(), heaters.end());
int res = INT_MIN;
int i, j = 0;
for (i = 0; i < m; ++i) {
while (j < n - 1 && abs(heaters[j + 1] - houses[i]) <= abs(heaters[j] - houses[i])) {
j++;
}
res = max(res, abs(houses[i] - heaters[j]));
}
return res;
}
};
class Solution(object):
def findRadius(self, houses, heaters):
"""
:type houses: List[int]
:type heaters: List[int]
:rtype: int
"""
m = len(houses)
n = len(heaters)
houses.sort()
heaters.sort()
i = 0
j = 0
res = 0
for i in range(m):
while j < n - 1 and abs(heaters[j + 1] - houses[i]) <= abs(heaters[j] - houses[i]):
j += 1
res = max(res, abs(houses[i] - heaters[j]))
return res
Binary search the neighboring heaters get max of min
- Notice we cannot sort hourses and then search each heater's position. A special cases
[1, 2, 3] 2
, the result is0
whereis it should be1
.
class Solution {
public:
int findRadius(vector<int>& houses, vector<int>& heaters) {
int n = heaters.size();
sort(heaters.begin(), heaters.end());
int res = INT_MIN;
for (int house : houses) {
int start = 0, end = n;
while (start < end) {
int mid = start + (end - start) / 2;
if (heaters[mid] < house)
start = mid + 1;
else
end = mid;
}
int dist1 = (start == n) ? INT_MAX : heaters[start] - house;
int dist2 = (start == 0) ? INT_MAX : house - heaters[start - 1];
res = max(res, min(dist1, dist2));
}
return res;
}
};
class Solution(object):
def findRadius(self, houses, heaters):
"""
:type houses: List[int]
:type heaters: List[int]
:rtype: int
"""
m = len(houses)
n = len(heaters)
heaters.sort()
i = 0
j = 0
res = float('-inf')
for i in range(m):
start = 0
end = n
while start != end:
mid = start + (end - start) / 2
if heaters[mid] < houses[i]:
start = mid + 1
else:
end = mid
dist1 = float('inf')
dist2 = float('inf')
if start != n:
dist1 = heaters[start] - houses[i]
if start != 0:
dist2 = houses[i] - heaters[start - 1]
res = max(res, min(dist1, dist2))
return res
410. Split Array Largest Sum¶
1011. Capacity To Ship Packages Within D Days¶
Binary solution
Same as the 410. Split Array Largest Sum
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int n = weights.size();
if (n < D) return 0;
int l = *max_element(weights.begin(), weights.end());
int h = accumulate(weights.begin(), weights.end(), 0);
while (l < h) {
int m = (l + h) / 2;
int c = 1; // need cut D-1 times
int sum = 0;
for (int w: weights) {
if (sum + w > m) {
sum = 0;
c++;
}
sum += w;
}
if (c > D) {
l = m + 1;
} else {
h = m;
}
}
return l;
}
};
875. Koko Eating Bananas¶
Binary search
Using the monotonic guessing approach, notice the trick in counting whether the given guess value is possible.
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int H) {
int N = piles.size();
if (N > H)
return 0;
int l = 1;
long r = 10e9;
while (l < r) {
int k = l + (r - l) / 2;
int hour = 0;
for (int p : piles) {
if (k >= p) {
hour++;
} else {
hour += (p + k - 1) / k;
}
}
if (hour > H) { // K is too large,
l = k + 1;
} else {
r = k;
}
}
return l;
}
};
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int H) {
int N = piles.size();
if (N > H)
return 0;
int l = 1;
long r = 10e9;
while (l < r) {
int k = l + (r - l) / 2;
int hour = 0;
for (int p : piles) {
hour += (p + k - 1) / k;
}
if (hour > H) { // K is too large,
l = k + 1;
} else {
r = k;
}
}
return l;
}
};
1539. Kth Missing Positive Number¶
Naive Solution
- using multiple variables and keep loop invariant.
Binary Search
- Observe the relation: total missing positives before
A[m]
isA[m] - 1 - m
because the indexm
andA[m]
is related to the missing positives thus can be used for counting. - the bisection condition can be interpreted as a boolean predicate: "whether the number of missing positives before
A[m]
is no less thank
?"
class Solution {
public:
int findKthPositive(vector<int>& arr, int k) {
if (arr.empty()) return k;
int missing_cnt = arr[0] - 1;
if (missing_cnt >= k) return k;
int prev = arr[0];
for (int i = 1; i < arr.size(); ++i ) {
if (!(arr[i] == prev || arr[i] == prev + 1)) {
int skip = arr[i] - prev - 1;
if (missing_cnt + skip >= k) {
return prev + k - missing_cnt;
}
missing_cnt +=skip;
}
prev = arr[i];
}
return (prev + k - missing_cnt);
}
};
class Solution {
public:
int findKthPositive(vector<int>& arr, int k) {
int l = 0, r = arr.size();
while (l < r) {
int m = l + (r - l) / 2;
if (arr[m] - 1 - m < k) {
l = m + 1;
} else {
r = m;
}
}
return l + k;
}
};
1482. Minimum Number of Days to Make m Bouquets¶
Solution Binary Search
- Use a subroutine to compute whether the constrain can be meet or not.
- The search is looking for the whether m bouquets is possible, meet the binary pattern "no less than". So that we use
if(cnt_m < m)
and the return values is l.
class Solution {
public:
int minDays(vector<int>& bloomDay, int m, int k) {
int l = *min_element(bloomDay.begin(), bloomDay.end());
int r = *max_element(bloomDay.begin(), bloomDay.end());
if (bloomDay.size() < m * k) return -1;
while (l < r) {
int mid = l + (r - l) / 2;
int cnt_k = 0;
int cnt_m = 0;
for (int d: bloomDay) {
if (d > mid) {
cnt_k = 0;
} else {
cnt_k++;
if (cnt_k == k) {
cnt_m++;
cnt_k = 0;
}
}
}
if (cnt_m < m) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
};
1283. Find the Smallest Divisor Given a Threshold¶
Solution Binary search
- Notice the specific divisor calculation. under this divisor operation, no matter how large the divisor is, the sum is always greater than
nums.size()
, if not, the solution is not guaranteed. so the threshold cannot smaller thannums.size()
. This also indicate that the minimum divisor is less than or eaual tomax(nums)
. - in the bsection predicate, notice this time the condition becomes
if (res > target)
essentially, theif (f(mid) < target)
in the binary search templates is sayingmid
andf(mid)
are positive correlation. here themid
andres
are negative correlation.
class Solution {
public:
int smallestDivisor(vector<int>& nums, int threshold) {
int l = 1;
int r = *max_element(nums.begin(), nums.end());
while (l < r) {
int m = l + (r - l) / 2;
int res = 0;
for (int num: nums) {
res += (num + m - 1) / m;
}
if (res > threshold) {
l = m + 1;
} else {
r = m;
}
}
return l;
}
};
1231. Divide Chocolate¶
Solution Binary search
- The key difference between this problem and 410. Split Array Largest Sum is this problem asks for maximizing the smallest sweetness of the pieces, whereas the 410. Split Array Largest Sum asks minimizing the largest piece. Support
k
cuts generatem
outcomes S = \{s_1^{|k|}, s_2^{|k|}, \cdots, s_m^{|k|}\}, this problem is to find the value of \operatorname*{argmax}_m (\operatorname*{argmax}_k (S)). - Imagine you guessed a value
m
, which is the maximum sweetness you can get from the smallest sweetness piece of all cuts. How to test whether the valuem
is possible? If possible, we will increase it to maximize it; if not, we will still keep it a candidate.
Same problem as 183. Wood cut.
class Solution {
public:
int maximizeSweetness(vector<int>& sweetness, int K) {
int start = *min_element(sweetness.begin(), sweetness.end());
int end = accumulate(sweetness.begin(), sweetness.end(), 0);
while (start < end) {
int mid = (start + end + 1) / 2;
int sum = 0;
int cuts = 0;
for (int s: sweetness) {
if ((sum += s) >= mid) {
sum = 0;
if (++cuts > K)
break;
}
}
if (cuts > K) {
// because >= mid above guarentee the "no less than" the guess.
// if cuts > K, mid could be the right answer and should be returned.
// Remember the binary search invariance requies not miss any
// answer in each iteration.
start = mid;
} else {
end = mid - 1;
}
}
return start;
}
};
Note
Compare the binary search solution of problem of 1231. Divide Chocolate and 410. Split Array Largest Sum. Notice how different in checking the number of cuts. It exceeds the limit K
, for max and min case, it indicate a very trivial difference in meaning.
Copy books (lintcode)¶
183. Wood cut (lintcode)¶
Description
Given n pieces of wood with length L[i]
(integer array). Cut them into small pieces to guarantee you could have equal or more than k pieces with the same length. What is the longest length you can get from the n pieces of wood? Given L & k, return the maximum length of the small pieces. You couldn't cut wood into float length. If you couldn't get >= k pieces, return 0.
Solution 1 Binary search
- It requires getting equal or more than
k
pieces of wood with the same length. So you have to cut the wood to fulfill the requirement. However, you need to promise that each of thek
wood is the longest that is possible. - Imagine that you are given a bunch of wood to cut. How would you do it? You probably want to try to make one cut and see whether you can make it or not. If not, you may want to make two cuts, and so on. But how could you program such a solution? It is very hard.
- Start thinking about the length seems a good option. Suppose you know your final maximum length. You would be able to make the cut accordingly. Now given a length out of guessing, can you verify whether it going to work or not? Yes, you can! That's the core idea of this solution.
class Solution {
public:
int woodCut(vector<int> &L, int k) {
if(L.empty()) return 0;
int maxlen = *max_element(L.begin(), L.end());
if(k == 0) return maxlen;
int start = max(1, maxlen/k), end = maxlen;
while(start < end) {
int mid = start + (end - start) / 2;
int count = 0;
for(int len : L) {
count += len / (mid + 1);
}
if(count >= k)
start = mid + 1;
else
end = mid;
}
int count = 0;
for(int len : L) count += len/start;
return count >= k ? start : 0;
}
};
774. Minimize Max Distance to Gas Station¶
Solution 1 Binary search
- It is very similar to the problem Wood cut. You just need to take care of the accuracy of the results, namely also the int/double casts. It is also the hard part of the problem.
- Notice the
count
variable is int type, you should test your solution expecially for the linecount += dist[i] / mid
;
class Solution {
public:
double minmaxGasDist(vector<int>& stations, int K) {
int n = stations.size();
vector<int> dist(n, 0); // dist[0] = null;
int d = 0;
for (int i = 1; i < n; ++i) {
dist[i] = stations[i] - stations[i - 1];
d = max(d, dist[i]);
}
double low = 0, high = d;
while (low + 0.000001 < high) {
double mid = low + (high - low) / 2;
int count = 0;
for (int i = 1; i < n; ++i) {
count += dist[i] / mid;
}
if (count > K) { // mid is too small
low = mid;
} else {
high = mid;
}
}
return low;
}
};
644. Maximum Average Subarray II¶
- First try to understand why the constrain is "greater than or equal than k". You find that this constrain will ensure the solution exists and the problem is interesting.
- Notice the monotonicity of the "average sum" values, namely for a given value, if it doesn't fulfill the constrain (length >= k & max(avg)), you can eliminate half of the values from the solution space.
- Your binary search predicate will be to test whether there exists a subarray with length greater than
k
and an average value is larger than themid
. - We use a trick to verify the constrains. The tricky thing is that the two constrains are not checked separately, they need to be work together in order to achieve better complexity. The length constrain is ensured partly by the "skip" indexing (
i - k
), partly by keeping the smallest average before the current considered subarray.
Key Math Insight
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double start = *min_element(nums.begin(), nums.end());
double end = *max_element(nums.begin(), nums.end());
while (lower + 0.00001 < upper) {
double mid = lower + (upper - lower) / 2;
if (isLarger(nums, mid, k)) { // is average value >= mid?
lower = mid;
} else {
upper = mid;
}
}
return lower;
}
/* return true if a greater average value is possible */
bool isLarger(vector<int>& nums, double mid, int k) {
int n = nums.size();
double sums = 0, prev = 0, prev_min = 0;
for (int i = 0; i < k; i++) {
sums += nums[i] - mid;
}
// we keep looking for whether a subarray sum of length >= k in array
// "sums" is possible to be greater than zero. If such a subarray exist,
// it means that the target average value is greater than the "mid" value.
if (sums >= 0) {
return true;
}
// we look at the front part of sums that at least k element apart from i.
// If we can find the minimum of the sums[0], sums[1], ..., sums[i - k]
// and check if sums[i] - min(sums[0], sums[1], ..., sums[i - k]) >= 0.
// If this is the case, it indicate, there exist a subarray of length >= k
// with sum greater than 0 in sums. we can return ture.
for (int i = k; i < n; i++) {
sums += nums[i] - mid;
prev += nums[i - k] - mid;
prev_min = min(prev_min, prev);
if (sums >= prev_min)
return true;
}
return false;
}
};
778. Swim in Rising Water¶
- In This problem we are trying to find a path, in which the maximum element in the path among all paths is minimum. Meaning we look for a target value in the grid, such that there exist a path from
grid[0][0]
togrid[n-1][n-1]
which includes this value and it is the maximum value in the path.
class Solution {
int x[4] = {0, -1, 0, 1};
int y[4] = {-1, 0, 1, 0};
public:
int swimInWater(vector<vector<int>>& grid) {
int n = grid.size();
int begin = grid[0][0], end = n * n - 1;
// binary search find a path with mini elevation
while (begin < end) {
int mid = begin + (end - begin) / 2;
if (pathExist(grid, mid)) {
end = mid;
} else {
begin = mid + 1;
}
}
return begin;
}
bool pathExist(vector<vector<int>> & grid, int mid) {
int n = grid.size();
vector<vector<int>> visited(n, vector<int>(n, 0));
return dfs_helper(grid, visited, n, mid, 0, 0);
}
bool dfs_helper(vector<vector<int>> & grid, vector<vector<int>>& visited,
int n, int mid, int i, int j) {
visited[i][j] = 1;
for (int k = 0; k < 4; ++k) {
int a = i + x[k];
int b = j + y[k];
if (a < 0 || a >= n || b < 0 || b >= n || visited[a][b] == 1 || grid[a][b] > mid) continue;
if (a == n - 1 && b == n - 1) return true;
if (dfs_helper(grid, visited, n, mid, a, b)) return true;
}
return false;
}
};
483 Smallest Good Base¶
Solution 1 Binary search
- This problem requires a bit reasoning to achieve the solution.
- The starting point is realy mean what's asking by the problem. Here it is asking a minimum base that represent the given number
n
in a representation like binary representation. For example:13 = 3^0 + 3^1 + 3^2
so13
can be representd as111
(base 3). - First of all, there is a special case that such a base may not exist. (precisely, we should seperate the special case when
n = (n-1)^0 + (n-1)^1
; With this special case in mind, we can use binary search to iterate through eachm
from largest to smallest and check whether the correspondingk
is a good base of the given valuen
. Because whenm
is the largest,k
is the smallest, so if the bianry search find one it must be the smallestk
we are looking for. If binary search found nothing, we simpley return the special casen-1
.
class Solution {
public:
string smallestGoodBase(string n) {
long long num = stoll(n);
/* for each lenght of the potentional representation,
* n = 1 + k + ... + k^{i-1} = (k^i-1)/(k-1), lower bound k is 2,
* we have 2^i-1 = n ==> upper bound i = log2(n+1). */
for (int i = log2(num + 1); i >= 2; --i) {
/* upper bound is obtained by n = 1 + k + k^2 ... + k^(i-1) > k^(i-1),
* n > k^(i-1) ==> k < n^(1/(i-1)); */
long long left = 2, right = pow(num, 1.0 / (i - 1)) + 1;
while (left < right) {
long long mid = left + (right - left) / 2;
long long sum = 0;
/* calculate i digits value with base "mid" */
for (int j = 0; j < i; ++j) {
sum = sum * mid + 1;
}
/* binary search for the mid (good base) */
if (sum == num)
return to_string(mid);
if (sum < num)
left = mid + 1;
else
right = mid;
}
}
return to_string(num - 1);
}
};
658. Find K Closest Elements¶
373. Find K Pairs with Smallest Sums¶
378. Kth Smallest Element in a Sorted Matrix¶
Solution 1 Binary Search
- The idea of using binary search for this problem my not be straightforward. But the method is very important. The idea is very similar to the problem Search in a rotated sorted array.
- Because the matrix is sorted row wise and column wise, there are some ordering information we can make use of.
- Notice we are not try to search using the matrix index, we are searching the matrix element value. Compare to the problem 287. Find the Duplicate Number.
- The comparison
if (count < k)
isn't include mid explicitly. but thecount
is some functionf(mid)
, with the currentmid
, the count value is unique and can be use to test a condition that decide whichside
we can go to shrink the range the target value is possible in.
class Solution {
public:
int kthSmallest(vector<vector<int>>& matrix, int k) {
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
int start = matrix[0][0], end = matrix[m - 1][n - 1];
while (start < end) {
int mid = start + (end - start) / 2;
int count = 0;
for (int i = 0; i < m; ++i) {
count += upper_bound(matrix[i].begin(), matrix[i].end(), mid) - matrix[i].begin();
}
if (count < k) { // notice no mid here, but count is a function of mid.
start = mid + 1;
} else {
end = mid;
}
}
return start;
}
};
Solution 2 Priority Queue
- Notice when the
k <= n^2
, indexj < matrix.size()
will also make it work.
class Solution {
public:
int kthSmallest(vector<vector<int>>& matrix, int k) {
priority_queue<int> pq;
for (int i = 0; i < matrix.size(); ++i) {
for (int j = 0; j < matrix[0].size(); ++j) {
pq.push(matrix[i][j]);
if (pq.size() > k)
pq.pop();
}
}
return pq.top();
}
};
668. Kth Smallest Number in Multiplication Table¶
Solution 1 Binary search
- While this problem looks simple. But it really isn't unless you observed the following.
- The condition used for binary search is "whether there are k smaller elements in the range [start, mid]". You are looking for the smallest number that has k elements less than or equal to it. Like in the problem Kth Smallest Element in a Sorted Matrix, we will move the number not the index.
- We move the
start
orend
appropriately based on this condition, if there are more than k, we shrink the range by reduce end:end = mid
. If there are less than k numbers, we increasebegin
hopefully to makemid
larger so as to have close to k numbers in the range of[1, mid]
. - When
being == end
, we've located the kth number desired. In casek > m*n
, we will gotbegin == end < k
, which is not a solution. - In counting how many elements less than mid, you have to be clever a bit by using the feature that this matrix is a multiplicative table. That is for row
i
, you can at most havex/i
number smaller thanx
, why? - Follow up: Does the kth element will be in the range of
[1, m*n]
?
class Solution {
public:
int findKthNumber(int m, int n, int k) {
int begin = 1, end = m * n;
while (begin < end) {
int mid = begin + (end - begin) / 2;
int count = 0;
for (int i = 1; i <= m; ++i) {
count += min(mid / i, n);
}
if (count < k)
begin = mid + 1;
else
end = mid;
}
return begin;
}
};
719. Find K-th Smallest Pair Distance¶
Solution 1 Priority Queue TLE
class Solution {
public:
int smallestDistancePair(vector<int>& nums, int k) {
priority_queue<int> pq;
for (int i = 0; i < nums.size(); ++i) {
for (int j = i + 1; j < nums.size(); ++j) {
int dist = abs(nums[i] - nums[j]);
if (pq.size() < k) {
pq.push(dist);
} else if (dist < pq.top()) {
pq.push(dist), pq.pop();
}
}
}
return pq.top();
}
};
Solution 2 Binary search
- Similar to Problem 668. Kth Smallest Number in Multiplication Table.
- The problem is complicated at the first glance. A brute force solution generates all the absolute distances and then sort to find the kth smallest one.
- We found it is potentially a searchable scenario if we sort the elements. We have range
[min_distance, max_distance]
. We search a distance in this range such that there are exactly k pairs distance including itself. If the count of pair distance less than k, we try to increase it buystart = mid + 1
, vice versa. - When the binary search loop stops, if the result exist,
start
point to the distance we are searching. Since this problem guarrantee solution exist, we returnstart
.
class Solution {
public:
int smallestDistancePair(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int start = nums[1] - nums[0];
for (int i = 2; i < nums.size(); ++i) {
start = min(start, nums[i] - nums[i - 1]);
}
int end = nums.back() - nums[0];
while (start < end) {
int mid = start + (end - start) / 2;
// count how many absolute differences that <= mid;
int count = 0;
for (int i = 0; i < nums.size(); ++i) {
int j = i;
while (j < nums.size() && nums[j] - nums[i] <= mid) j++;
count += j - i - 1;
}
if (count < k) {
start = mid + 1;
} else {
end = mid;
}
}
return start;
}
};
Solution 3 Using binary search to optimize the counting
- You can also write your own binary search routine
upper_bound
.
class Solution {
public:
int smallestDistancePair(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int start = nums[1] - nums[0];
for (int i = 2; i < nums.size(); ++i) {
start = min(start, nums[i] - nums[i - 1]);
}
int end = nums.back() - nums[0];
while (start < end) {
int mid = start + (end - start) / 2;
// count how many absolute differences that <= mid;
int count = 0;
/*
for (int i = 0; i < nums.size(); ++i) {
int j = i;
while (j < nums.size() && nums[j] - nums[i] <= mid) j++;
count += j - i - 1;
}
*/
// optimize the counting use binary search (nested binary search)
for (int i = 0; i < nums.size(); ++i) {
auto iter = upper_bound(nums.begin() + i, nums.end(), nums[i] + mid);
count += iter - (nums.begin() + i) - 1;
}
if (count < k) {
start = mid + 1;
} else {
end = mid;
}
}
return start;
}
};
786. K-th Smallest Prime Fraction¶
- You should find seek to find the monotonic pattern of the fraction and think how to search it effectively. To use binary search you need to draw an imaginary matrix to consider how can you search effectively.
class Solution {
public:
vector<int> kthSmallestPrimeFraction(vector<int>& A, int K) {
int n = A.size();
double l = 0, r = 1.0;
while (l < r) {
double m = (l + r) / 2;
// calculate how many smaller on the right
int cnt = 0;
double mx = 0;
int p, q;
int j = 1;
for (int i = 0; i < n - 1; ++i) {
while (j < n && A[i] > A[j] * m) ++j;
// int j = upper_bound(A.begin() + i, A.end(), A[i] / m) - A.begin();
cnt += (n - j);
if (n == j) break;
double fraction = (double) A[i] / (double) A[j];
if (fraction > mx) {
p = A[i];
q = A[j];
mx = fraction;
}
}
if (cnt == K) {
return {p, q};
}
if (cnt > K) {
r = m;
} else if (cnt < K) {
l = m;
}
}
return {};
}
};
1631. Path With Minimum Effort¶
Solution 1 Binary search + BFS
- Because we are searching for the smallest effort of all paths. If the proposed solution is not possible, namely, all paths have effort greater than the proposed solution (the proposed value is too small). We need to increase the
start
in binary search. We are looking for the no-less-than x in the binary search.
Solution 2 Dijkstra
- If you can change the problem into searching a weighted graph with edge weights, which are the absolute differences (effort). Since the weights are all positives, using Dijkstra algorithm can find the shortest path in the measure of effort.
class Solution {
vector<int> dx={0, 1, 0, -1};
vector<int> dy={-1,0, 1, 0};
public:
int minimumEffortPath(vector<vector<int>>& heights) {
int m = heights.size();
int n = m == 0 ? 0 : heights[0].size();
int start = 0, end = 10e6;
while (start < end) {
int mid = (start + end) / 2;
if (!pathPossible(heights, mid)) {
start = mid + 1;
} else {
end = mid;
}
}
return start;
}
bool pathPossible(vector<vector<int>>& heights, int val) {
int m = heights.size();
int n = m == 0 ? 0 : heights[0].size();
queue<vector<int>> q;
q.push({0, 0});
set<int> visited;
visited.insert(0);
while (!q.empty()) {
vector<int> t = q.front();
int x = t[0];
int y = t[1];
q.pop();
if (x == m - 1 && y == n - 1)
return true;
for (int k = 0; k < 4; k++) {
int a = x + dx[k];
int b = y + dy[k];
if (a < 0 || a >= m || b < 0 || b >= n) continue;
if (val < abs(heights[a][b] - heights[x][y])) continue;
if (visited.count(a * n + b) > 0) continue;
q.push({a, b});
visited.insert(a * n + b);
}
}
return false;
}
};
class Solution {
private int[] d = {0, 1, 0, -1, 0};
public int minimumEffortPath(int[][] heights) {
int lo = 0, hi = 1_000_000;
while (lo < hi) {
int effort = lo + (hi - lo) / 2;
if (isPath(heights, effort)) {
hi = effort;
}else {
lo = effort + 1;
}
}
return lo;
}
private boolean isPath(int[][] h, int effort) {
int m = h.length, n = h[0].length;
Queue<int[]> q = new LinkedList<>();
q.offer(new int[2]);
Set<Integer> seen = new HashSet<>();
seen.add(0);
while (!q.isEmpty()) {
int[] cur = q.poll();
int x = cur[0], y = cur[1];
if (x == m - 1 && y == n - 1) {
return true;
}
for (int k = 0; k < 4; ++k) {
int r = x + d[k], c = y + d[k + 1];
if (0 <= r && r < m && 0 <= c && c < n &&
effort >= Math.abs(h[r][c] - h[x][y]) && seen.add(r * n + c)) {
q.offer(new int[]{r, c});
}
}
}
return false;
}
}
class Solution {
public:
int minimumEffortPath(vector<vector<int>>& heights) {
int m = heights.size();
int n = heights[0].size();
vector<vector<int>> dist(m, vector<int>(n, INT_MAX)); // min distance found so far.
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
int d[5] = {0, 1, 0, -1, 0};
pq.push({0, 0}); // first: min effort, second: encoded (x, y) (=x * n + y);
while (!pq.empty()) {
pair<int, int> t = pq.top(), pq.pop();
int effort = t.first;
int x = t.second / n;
int y = t.second % n;
if (x == m - 1 && y == n - 1)
return effort;
for (int k = 0; k < 4; ++k) {
int a = x + d[k];
int b = y + d[k + 1
if (a < 0 || a >= m || b < 0 || b >= n) continue;
// update neighboring node, effort=min effort before visit node(a,b)
int currEffort = max(effort, abs(heights[a][b] - heights[x][y]));
if (dist[a][b] > currEffort) {
dist[a][b] = currEffort;
pq.push({currEffort, a * n + b});
}
}
}
return -1;
}
};
class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
m, n = map(len, [heights, heights[0]])
efforts = [[math.inf] * n for _ in range(m)]
efforts[0][0] = 0
heap = [(0, 0, 0)]
while heap:
effort, x, y = heapq.heappop(heap);
if (x, y) == (m - 1, n - 1):
return effort
for i, j in (x, y - 1), (x, y + 1), (x - 1, y), (x + 1, y):
if i < 0 or i >= m or j < 0 or j >= n:
continue
currEffort = max(effort, abs(heights[x][y] - heights[i][j]))
if efforts[i][j] > currEffort:
efforts[i][j] = currEffort
heapq.heappush(heap, (currEffort, i, j))
1102. Path With Maximum Minimum Value¶
Solution 1 Binary Search + BFS
- Again, propose a possible value and use a
isValid
function to check the validity of the proposed solution.
Solution 2 BFS + PQ
- This solution can be thought as a variant of Dijkastra, but not the same.
Solution 3 Union Find
- We need to sort all the vertices by their values in descending order, then choose element from the vertices and use Union-Find to check connectivity of
A[0][0]
andA[m - 1][n - 1]
.
class Solution {
public:
int maximumMinimumPath(vector<vector<int>>& A) {
int m = A.size();
int n = A[0].size();
int start = 0, end = max(A[0][0], A[m - 1][n - 1]);
int res = 0, mid = 0;
while (start < end) {
mid = start + (end - start) / 2;
if (pathPossible(A, mid)) {
start = mid + 1;
} else {
end = mid;
}
}
return start;
}
bool pathPossible(vector<vector<int>>& A, int mid) {
int m = A.size();
int n = A[0].size();
queue<pair<int, int>> q;
q.emplace(0, 0);
vector<vector<int>> v(m, vector<int>(n, 0));
v[0][0] = 1;
int d[5] = {0, 1, 0, -1, 0};
while (!q.empty()) {
int x = q.front().first;
int y = q.front().second;
q.pop();
if (x == m - 1 && y == n - 1)
return true;
for (int k = 0; k < 4; ++k) {
int a = x + d[k];
int b = y + d[k + 1];
if (a < 0 || a >= m || b < 0 || b >= n || v[a][b] == 1) continue;
if (mid > A[a][b]) continue;
q.emplace(a, b);
v[a][b] = 1;
}
}
return false;
}
};
class Solution {
public:
int maximumMinimumPath(vector<vector<int>>& A) {
int m = A.size();
int n = A[0].size();
int res = INT_MAX;
priority_queue<pair<int, int>, vector<pair<int, int>>> pq; // max heap.
pq.emplace(A[0][0], 0);
vector<vector<int>> visited(m, vector<int>(n, 0));
visited[0][0] = -1;
int d[5] = {0, 1, 0, -1, 0};
while (!pq.empty()) {
pair<int, int> t = pq.top(); pq.pop();
int cost = t.first;
int x = t.second / n;
int y = t.second % n;
res = min(res, cost);
if (x == m - 1 && y == n - 1)
break;
for (int k = 0; k < 4; k++) {
int r = x + d[k];
int c = y + d[k + 1];
if (r < 0 || r >= m || c < 0 || c >= n || visited[r][c] < 0) continue;
pq.emplace(A[r][c], r * n + c);
visited[r][c] = -1;
}
}
return res;
}
};
Category 4 Binary search as an optimization routine¶
300 Longest Increasing Subsequence¶
Solution 1 DP
- The base case is single char.
f[i]
is the length of LIS from the begining.
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (n == nums.size()) return 0;
int f[n] = {0};
int res = 0;
for (int j = 0; j < n; j++) {
f[j] = 1;
for (int i = 0; i < j; i++) {
if (nums[i] < nums[j] && f[i] + 1 > f[j])
f[j] = f[i] + 1;
}
res = max(res, f[j]);
}
return res;
}
};
Solution 2 Using binary search
- The DP solution is
O(n^2)
. Using binary search could reduce toO(nlogn)
. - Binary search solution analysis. For each
i
, we are looking for the largestf
value that has smallestA
value. For example,A[0] = 5
could be ignored because of itsf
value is same asA[1] = 1
, which is smaller. In searching for the LIS, we prefer a small ending value when the length is the same. -
The following solution using a vector
b
to record the minimumA
value for each length of LIS (f
value), we use binary search to find the last value inb
that smaller that current valueA[i]
. If we found such a value inb
, we useA[i]
to replace the value next to the found value inb
).i 0 1 2 3 4 5 6 7 A 5 1 3 7 6 4 2 10 f 1 1 2 3 3 3 2 4 f[1] = 1, a[1] = 1 f[6] = 2, a[6] = 2 f[5] = 3, a[5] = 4 f[7] = 4, a[7] = 10
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> b;
for (int i = 0; i < nums.size(); ++i) {
int l = 0, r = b.size();
while (l < r) {
int m = l + (r - l) / 2;
if (b[m] < nums[i]) { // nums[i] is the target
l = m + 1;
} else {
r = m;
}
}
if (l == b.size()) // nums[i] greater than all element in b
b.push_back(nums[i]);
else // begin point to next element no less than the target nums[i].
b[l] = nums[i];
}
return b.size();
}
};
Alternatively, we could use lower_bound
.
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> b;
for (int i = 0; i < nums.size(); ++i) {
int l = lower_bound(b.begin(), b.end(), nums[i]) - b.begin();
if (l == b.size()) // nums[i] greater than all element in b
b.push_back(nums[i]);
else // begin point to next element no less than the target nums[i].
b[l] = nums[i];
}
return b.size();
}
};