Index of this series
- Linear table basis
- Tree structure foundation
- Sorting algorithm
- Search Search Algorithm
- To be continued
Binary search principle
Binary search is an algorithm applied to sequential sequences. In the process of search, we continuously narrow the search range on the premise of ensuring that the target value is in the search range, and finally find the target value
Firstly, the simplest binary search algorithm is introduced. This algorithm is used to locate the given target value. If it cannot be found, it returns - 1
First, set the two pointers at the beginning and end, and then compare them through the intermediate value (head + tail) > > 1. If the intermediate value is equal to the target value, the target value will be returned directly. Otherwise, perform the following operations
{ a r r [ m i d ] < x , h e a d = m i d + 1 a r r [ m i d ] > x , t a i l = m i d − 1 \begin{cases} arr[mid]<x,head=mid+1 \\ arr[mid] > x,tail = mid - 1 \end{cases} {arr[mid]<x,head=mid+1arr[mid]>x,tail=mid−1
The operation here is easy to understand. The goal is to ensure that the target is within the search range and narrow the search range
function binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h <= t) { mid = (t + h) >> 1 if (nums[mid] === target) return mid if (nums[mid] < target) h = mid + 1 else t = mid - 1 } return -1 }
01 model
00001111
This model is usually used to find the last 0 and the first 1
The last 0 here can be understood as the last value less than or equal to x;
The first 1 can be understood as the first value greater than or equal to x.
Here, take finding the first value greater than or equal to x as an example for reasoning
// 01-1 core part while (h < t) { // Middle pointer mid = (h + t) >> 1 // Narrow selection if (nums[mid] <= target) h = mid else t = mid - 1 }
// 01-1 core code function b_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = (h + t) >> 1 // mid = h + ((t - h) >> 1) if (nums[mid] >= target) t = mid else h = mid + 1 } return h }
Similarly, the 01-0 code is obtained
// 01-0 core code function s_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = h + ((t - h) >> 1) + 1 // In any case, one to the right // parseInt(`${(h + t + 1) / 2} `) / / find the middle value for odd numbers and the middle right for even numbers if (nums[mid] <= target) h = mid else t = mid - 1 } return h }
Bias of middle pointer
In the above code, you can find that the mid of 01-0 and 01-1 are slightly different
In the search sequence, if the number of elements is odd, the middle value they locate is the same; However, if it is an even number, 01-0 is positioned to the right of the middle, while 01-1 is positioned to the left of the middle
If it is right, there are two cases
- As mentioned above, odd numbers are positioned in the middle, and even numbers are positioned in the right of the middle
mid = parseInt(`${(h + t + 1) / 2}`)
- The pointer is one bit to the right
mid = ((h + t) >> 1) + 1 mid = h + ((t - h) >> 1) + 1 // In static languages, this way of writing can prevent overflow
Both cases work
Binary search questions
Square root of leetCode 69 x
This problem can be transformed into finding the last value less than or equal to x
function mySqrt(x: number): number { let h = 0, t = x, mid: number while (h < t) { mid = ((h + t) >> 1) + 1 if (mid ** 2 <= x) h = mid else t = mid - 1 } return h }
leetCode 35 search insertion location
Find the first position greater than or equal to x
function searchInsert(nums: number[], target: number): number { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = (h + t) >> 1 if (nums[mid] >= target) t = mid else h = mid + 1 } return (h === nums.length - 1 && nums[h] < target) ? h + 1 : h }
leetCode 1 sum of two numbers
In this problem, we use simple two-point search to accurately find the target value
First traverse the array, and then find it later t a r g e t − x target - x Value of target − x
Note that the premise of binary search is for ordered sequences, so we have to sort the original array first. Here we use the merge sort we learned before
type dataType = [val: number, ind: number] function binarySearch (nums: dataType[], l: number, target: number) { let h = l, t = nums.length - 1, mid: number while (h <= t) { mid = (h + t) >> 1 if (nums[mid][0] === target) return nums[mid][1] if (nums[mid][0] < target) h = mid + 1 else t = mid - 1 } return -1 } function twoSum(nums: number[], target: number): number[] { const data: dataType[] = [], ret: number[] = [] let ind: number nums.forEach((val, i) => data.push([val, i])) mergeSort(data, 0, nums.length - 1) for (let i = 0; i < data.length - 1; i++) { ind = binarySearch(data, i + 1, target - data[i][0]) if (ind > -1) ret.push(data[i][1], ind) } return ret } //*Merge sort function mergeSort (data: dataType[], l: number, r: number) { if (l >= r) return const mid = (l + r) >> 1 mergeSort(data, l, mid) mergeSort(data, mid + 1, r) const temp: dataType[] = [] let p1 = l, p2 = mid + 1 while (p1 <= mid || p2 <= r) { if (p2 > r || (p1 <= mid && data[p1][0] < data[p2][0])) temp.push(data[p1++]) else temp.push(data[p2++]) } temp.forEach((v, i) => data[l + i] = v) }
Of course, if C + + is used here, the efficiency of using sort through symbolic overloading will be higher
leetCode 34 finds the first and last positions of elements in a sorted array
This problem is a typical example of the application of model 01
The first position of the element is 01-1, and the last position is 01-0
//* 01-0 function s_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = ((h + t) >> 1) + 1 if (nums[mid] <= target) h = mid else t = mid - 1 } //*When looking for the second value, it is certain that there must be a target value on the field return h } //* 01-1 function b_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = (h + t) >> 1 if (nums[mid] >= target) t = mid else h = mid + 1 } return nums[h] === target ? h : -1 } function searchRange(nums: number[], target: number): number[] { let fir: number, sec: number fir = b_binarySearch(nums, target) if (fir === -1) return [-1, -1] sec = s_binarySearch(nums, target) return [fir, sec] }
The idea of the main program is also very simple. If the first occurrence position of the target value cannot be found, directly return [- 1, - 1], otherwise continue to find the last occurrence position (the two can coincide)
leetCode 1658 minimum operand to reduce x to 0
A very intuitive solution to this problem is to first count the prefix sum in the left and right directions. When traversing the left prefix sum, use dichotomy to act on the right prefix and array to find x - sums[l]
But this problem only needs to calculate the prefix sum from left to right once
Here we emphasize that the interval and of our previous study are as follows:
s
[
4
]
−
s
[
2
]
=
n
[
2
]
+
n
[
3
]
Namely
s
[
i
]
−
s
[
j
]
=
∑
n
=
j
n
=
i
−
1
a
n
s[4] - s[2] = n[2]+n[3] \ \ i.e. s[i]-s[j]=\sum_{n=j}^{n=i-1}a_n
s[4] − s[2]=n[2]+n[3], i.e. s[i] − s[j]=n=j Σ n=i − 1 an
And the one used here n [ i ] − s [ j ] n[i]-s[j] n[i] − s[j] is equivalent to statistics ( j , i ) (j, i) Number of elements between (j,i)
So when i and j are found, the result is
c
n
t
=
n
u
m
s
.
l
e
n
g
t
h
−
(
i
−
j
)
cnt = nums.length - (i - j)
cnt=nums.length−(i−j)
function binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h <= t) { mid = (h + t) >> 1 if (nums[mid] === target) return mid if (nums[mid] < target) h = mid + 1 else t = mid - 1 } return -1 } function minOperations(nums: number[], x: number): number { //*Building prefixes and arrays const sums = [0] nums.forEach((n, i) => sums.push(n + sums[i])) //*Left and right counting let j: number, cnt: number, ans = -1 for (let i = nums.length, s = 0; i >= 0; --i, s += nums[Math.max(i, 0)]) { j = binarySearch(sums, x - s) if (j === -1) continue cnt = nums.length - (i - j) if (cnt > nums.length) continue // This happens when I < = J if (ans === -1 || cnt < ans) ans = cnt } return ans }
The main function strategy here is to see whether there is a target value (x - s) from the prefix and in each cycle. If not, continue to the next cycle
leetCode 475 heater
The idea of this problem is very simple. First, sort the address of the heater (the premise of binary search), then traverse the house, find the location of the nearest heater in the house and record the distance
Finally, the maximum distance is the minimum range
//* 01-0 function s_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = ((h + t) >> 1) + 1 if (nums[mid] <= target) h = mid else t = mid - 1 } return nums[h] } //* 01-1 function b_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = (h + t) >> 1 if (nums[mid] >= target) t = mid else h = mid + 1 } return nums[h] } function findRadius(houses: number[], heaters: number[]): number { heaters = heaters.sort((i, j) => i - j) let maxRadius = -1, recentHeater: number houses.forEach(house => { recentHeater = Math.min(Math.abs(house - s_binarySearch(heaters, house)), Math.abs(b_binarySearch(heaters, house) - house)) maxRadius = Math.max(maxRadius, recentHeater) }) return maxRadius }
The practice here is to use two structures of 01 model, but if you master 01 model deeply enough, just use one
function b_binarySearch (nums: number[], target: number) { let h = 0, t = nums.length - 1, mid: number while (h < t) { mid = (h + t) >> 1 if (nums[mid] >= target) t = mid else h = mid + 1 } return h } function findRadius(houses: number[], heaters: number[]): number { heaters = heaters.sort((i, j) => i - j) let maxRadius = -1, a: number, b: number, ind: number houses.forEach(house => { ind = b_binarySearch(heaters, house) a = Math.abs(heaters[ind] - house) b = Math.abs(ind ? heaters[ind - 1] - house : a + 1) maxRadius = Math.max(maxRadius, Math.min(a, b)) }) return maxRadius }
The idea here is to first find the heater (01-1) behind the current house. If the heater is not the first, compare it with the previous one. Who is closer to the house
Through this question, we can find that if the 01 model cannot find the result (for example, 01-1 cannot find a value greater than or equal to the target value), the first element subscript will be returned
You can see that the result is also correct
leetCode 81 search rotation sort array II
This problem is very complicated to read, but the actual situation is shown in the figure below
Each time the middle value is taken, it is only compared with the head / tail. If it is in the non shaded area, just keep the pointers on both sides in the middle
Because of the particularity of this problem, four pointers are required
Two are fixed at the head and tail, and the other two are gradually narrowing intervals
function search(nums: number[], target: number): boolean { if (!nums.length) return false if (nums[0] === target || nums[nums.length - 1] === target) return true //*Set the head and tail of the two sections let l = 0, r = nums.length - 1 while (l < r && nums[l] === nums[0]) l++ while (l < r && nums[r] === nums[nums.length - 1]) r-- const head = l, tail = r //*Positioning element let mid: number while (l <= r) { mid = (l + r) >> 1 if (nums[mid] === target) return true //*If it's in the right section if (nums[mid] < nums[tail]) { if (nums[mid] < target && target <= nums[tail]) l = mid + 1 else r = mid - 1 } else { //*In the left section if (nums[head] <= target && target < nums[mid]) r = mid - 1 else l = mid + 1 } } return false }
leetCode 4 finds the median of two positively ordered arrays
To solve this problem, we must first have a consensus
With this consensus, we can start coding
function findK (nums1: number[], nums2: number[], l1: number, l2: number, k: number): number { //*Boundary condition judgment if (l1 === nums1.length) return nums2[l2 + k - 1] if (l2 === nums2.length) return nums1[l1 + k - 1] if (k === 1) return nums1[l1] < nums2[l2] ? nums1[l1] : nums2[l2] //*Processing of getting values from two arrays let cnt1 = Math.min(k >> 1, nums1.length - l1) let cnt2 = Math.min(k - cnt1, nums2.length - l2) //*Narrow down if (nums1[l1 + cnt1 - 1] < nums2[l2 + cnt2 - 1]) { return findK(nums1, nums2, l1 + cnt1, l2, k - cnt1) } return findK(nums1, nums2, l1, l2 + cnt2, k - cnt2) } function findMedianSortedArrays(nums1: number[], nums2: number[]): number { const cnt = nums1.length + nums2.length, mid = (nums1.length + nums2.length + 1) >> 1 const a = findK(nums1, nums2, 0, 0, mid) if (cnt % 2) return a const b = findK(nums1, nums2, 0, 0, mid + 1) return (a + b) / 2 }
First, set a function that can find the k-th element in the two arrays. If the length of the two operation arrays is an odd number, the middle value is the middle number; If it is an even number, you need to find the number to the right and average it
The boundary conditions here are also well understood. When one array is empty, it is equivalent to directly operating on the other array; When k is 1, you only need to compare the header elements of the two arrays to see who is smaller
When taking values from two arrays, it should be noted that the length of the array may not be enough k 2 \frac{k}{2} 2k, so you need to do some compatibility processing
The final narrowing operation is based on the consensus just mentioned
Although the coding of this problem is not like the traditional binary search, it is the same in thought
Binary search determines the target value through the comparison of intermediate values, and here the target value is determined through three boundary conditions;
Binary search reduces the range through left and right pointers. Here, it reduces the range through counting and head pointers.
Xi Wei~