二分搜索直觉和谓词函数
二进制搜索算法用于许多编码问题,通常乍一看并不是很明显。但是,肯定有直觉和特定条件可能暗示使用二分搜索。在本文中,我们尝试开发一种二分搜索的直觉。
二分搜索简介
它是一种在排序序列中搜索特定元素、下限或上限的有效算法。如果这是您第一次遇到二进制搜索算法,那么您可能需要在继续之前参考这篇文章:二进制搜索算法背后的基本逻辑。
什么是单调函数?
您可能已经知道这是一个数学概念。
- 它们是遵循特定顺序的函数。
- 用数学术语来说,函数的斜率总是非负或非正的。
- 单调性是使用二分搜索的基本要求。回想一下,二进制搜索只能应用于排序数组[单调函数]。
例如一个不递增的数字数组:
![](https://mangodoc.oss-cn-beijing.aliyuncs.com/geek8geeks/Binary_Search_Intuition_and_Predicate_Functions_0.jpg)
单调函数
什么是谓词函数?
它们是接受输入并返回单个输出 TRUE 或 FALSE 的函数。
例如,一个接受整数的函数,如果整数大于 0 则返回 true,否则返回 false;
伪代码:
bool isPositive (int input) {
if (input > 0)
return true;
else
return false;
}
单调谓词函数:
- 单调谓词函数如下所示:
![](https://mangodoc.oss-cn-beijing.aliyuncs.com/geek8geeks/Binary_Search_Intuition_and_Predicate_Functions_1.jpg)
单调谓词函数
- 另一方面,下面的这个函数是一个谓词函数,但不是单调的:
![](https://mangodoc.oss-cn-beijing.aliyuncs.com/geek8geeks/Binary_Search_Intuition_and_Predicate_Functions_2.jpg)
不单调
- 可以观察到,在单调谓词函数中,值可以从真变为假,反之亦然,最多一次。可以将其可视化为 0 和 1 的数组,并检查该数组是否按非递增/非递减顺序排序。
二分搜索和单调谓词函数有什么关系?
任务是在给定数组中找到第一个真值:
![](https://mangodoc.oss-cn-beijing.aliyuncs.com/geek8geeks/Binary_Search_Intuition_and_Predicate_Functions_3.jpg)
单调谓词函数
朴素方法:朴素方法从头到尾遍历数组,第一次看到 TRUE 就中断,并返回索引。
时间复杂度: O(n)
高效的方法:但是,更好的方法是利用数组形成单调函数的事实,这意味着可以应用二进制搜索,这比线性搜索要好。
时间复杂度: O(logn)
二分搜索的直觉:
- 出现单调函数,例如。一个排序数组。
- 需要找出满足/不满足某个条件的最小/最大值。
- 可以形成单调谓词函数,并且需要一个过渡点。
示例:巧克力问题
问题陈述:给定一个由N个巧克力棒高度组成的数组arr[] ,任务是找到对所有巧克力进行水平切割的最大高度,使得巧克力的剩余总量至少为K。
例子:
Input: K = 7, arr[] = {15, 20, 8, 17}
Output: 15
Explanation: If a cut is made at height 8 the total chocolate removed is 28
and chocolate wasted is 28 – 7 = 21 units.
If a cut is made at height 15 then chocolate removed is 7
and no chocolate is wasted.
Therefore 15 is the answer.
Input: K = 12 arr[] = {30, 25, 22, 17, 20}
Output: 21
Explanation: After a cut at height 18, the chocolate removed is 25
and chocolate wastage is (25 – 12) = 13 units.
But if the cut is made at height 21 is made then 14 units of chocolate is removed
and the wastage is (14 – 12) = 2 which is the least. Hence 21 is the answer
关键观察:与问题相关的主要观察是
- 最终输出必须大于等于 0,因为这是可以进行水平切割的最小高度。
- 最终输出还必须小于或等于所有巧克力的最大高度,因为高于该高度的所有切割都会产生相同的结果。
- 现在有一个下限和上限。
- 现在需要找到巧克力切出大于或等于 K 的第一个点。
- 我们显然可以对此使用线性搜索,但这将是线性时间复杂度 [O(N)]。
- 但是,由于可以形成单调谓词函数,我们不妨使用二分搜索,它在对数时间复杂度 [O(logN)] 下要好得多。
方法:这可以使用二分搜索来解决,因为存在单调谓词函数。按照步骤:
- 我们已经从以下事实中得到了二分搜索的提示:我们想要“最大高度 [极值],其中巧克力剩余量的所需总和至少为K [要满足的条件]”。
- 我们可以创建一个谓词函数,它获取水平切割的高度,如果巧克力切割大于或等于 7(第一个示例中的 k = 7),则返回 true。
请参阅下面给出的插图以更好地理解
插图:
Take the following case as an example: K = 7, arr[] = {15, 20, 8, 17}. Initially the maximum as 20 and the minimum as 0.
- Maximum = 20, Minimum = 10: Now mid = (20+0)/2 = 10.
Cutting at a height of 10 gives, ( (15 – 10) + (20 – 10) + (0) + (17 – 10) ) = 22
Now 22 >= 7 but we need to find the maximum height at which this condition is satisfied.
So now change search space to [20, 10] both 10 and 20 include. (Note that we need to keep 10 in the search space as it could be the first true for the predicate function)
Update the minimum to 10. - Maximum = 20, Minimum = 10: Now mid = (20+10)/2 = 15.
Cutting at a height of 15 gives, ( (15 – 15) + (20 – 15) + (0) + (17 – 15) ) = 7
Now 7 >= 7 but we need to find the maximum height at which this condition is satisfied.
So now change search space to [20, 15] both 15 and 20 include. - Maximum = 20, Minimum = 15: Now new mid = (20+15)/2 = 17.
Cutting at a height of 17 gives, ( (0) + (20 – 17) + (0) + (17 – 17) ) = 3
Now 3 < 7 so we can remove 17 and all heights greater than 17 as all of them are guaranteed to be false. (Note that here we need to exclude 17 from the search space as it does not even satisfy the condition)
Now2 elements left in the search space 15 and 16.
So we can break the binary search loop where we will use the condition (high – low > 1). - Now first check if the condition is satisfied for 16, if yes then return 16, else return 15.
- Here since 16 does not satisfy the condition. Soreturn 15, which indeed is the correct answer.
![](https://mangodoc.oss-cn-beijing.aliyuncs.com/geek8geeks/Binary_Search_Intuition_and_Predicate_Functions_6.jpg)
Predicate Function Table
下面是上述方法的实现。
C++
// C++ program to implement the approach
#include
using namespace std;
// Predicate function
int isValid(int* arr, int N, int K,
int height)
{
// Sum of chocolate cut
int sum = 0;
// Finding the chocolate cut
// at this height
for (int i = 0; i < N; i++) {
if (height < arr[i])
sum += arr[i] - height;
}
// Return true if valid cut
return (sum >= K);
}
int maxHeight(int* arr, int N, int K)
{
// High as max height
int high = *max_element(arr, arr + N);
// Low as min height
int low = 0;
// Mid element for binary search
int mid;
// Binary search function
while (high - low > 1) {
// Update mid
mid = (high + low) / 2;
// Update high/low
if (isValid(arr, N, K, mid)) {
low = mid;
}
else {
high = mid - 1;
}
}
// After binary search we get 2 elements
// high and low which we can manually compare
if (isValid(arr, N, K, high)) {
return high;
}
else {
return low;
}
}
// Driver code
int main()
{
// Defining input
int arr[4] = { 15, 20, 8, 17 };
int N = 4;
int K = 7;
// Calling the function
cout << maxHeight(arr, N, K);
return 0;
}
Java
// java program to implement the approach
import java.util.Arrays;
class GFG {
// Predicate function
static boolean isValid(int[] arr, int N, int K,
int height) {
// Sum of chocolate cut
int sum = 0;
// Finding the chocolate cut
// at this height
for (int i = 0; i < N; i++) {
if (height < arr[i])
sum += arr[i] - height;
}
// Return true if valid cut
return (sum >= K);
}
static int maxHeight(int[] arr, int N, int K) {
// High as max height
int[] a = arr.clone();
Arrays.sort(a);
int high = a[a.length - 1];
// Low as min height
int low = 0;
// Mid element for binary search
int mid;
// Binary search function
while (high - low > 1) {
// Update mid
mid = (high + low) / 2;
// Update high/low
if (isValid(arr, N, K, mid)) {
low = mid;
} else {
high = mid - 1;
}
}
// After binary search we get 2 elements
// high and low which we can manually compare
if (isValid(arr, N, K, high)) {
return high;
} else {
return low;
}
}
// Driver code
public static void main(String[] args)
{
// Defining input
int arr[] = { 15, 20, 8, 17 };
int N = 4;
int K = 7;
// Calling the function
System.out.println(maxHeight(arr, N, K));
}
}
// This code is contributed by saurabh_jaiswal.
Python3
# Python code for the above approach
# Predicate function
def isValid(arr, N, K, height):
# Sum of chocolate cut
sum = 0
# Finding the chocolate cut
# at this height
for i in range(N):
if (height < arr[i]):
sum += arr[i] - height
# Return true if valid cut
return (sum >= K)
def maxHeight(arr, N, K):
# High as max height
high = max(arr)
# Low as min height
low = 0
# Mid element for binary search
mid = None
# Binary search function
while (high - low > 1):
# Update mid
mid = (high + low) // 2
# Update high/low
if (isValid(arr, N, K, mid)):
low = mid
else:
high = mid - 1
# After binary search we get 2 elements
# high and low which we can manually compare
if (isValid(arr, N, K, high)):
return high
else:
return low
# Driver code
# Defining input
arr = [15, 20, 8, 17]
N = 4
K = 7
# Calling the function
print(maxHeight(arr, N, K))
# This code is contributed by Saurabh Jaiswal
C#
// C# program to implement the approach
using System;
public class GFG {
// Predicate function
static bool isValid(int[] arr, int N, int K,
int height) {
// Sum of chocolate cut
int sum = 0;
// Finding the chocolate cut
// at this height
for (int i = 0; i < N; i++) {
if (height < arr[i])
sum += arr[i] - height;
}
// Return true if valid cut
return (sum >= K);
}
static int maxHeight(int[] arr, int N, int K) {
// High as max height
int[] a = new int[arr.Length];
a =arr;
Array.Sort(a);
int high = a[a.Length - 1];
// Low as min height
int low = 0;
// Mid element for binary search
int mid;
// Binary search function
while (high - low > 1) {
// Update mid
mid = (high + low) / 2;
// Update high/low
if (isValid(arr, N, K, mid)) {
low = mid;
} else {
high = mid - 1;
}
}
// After binary search we get 2 elements
// high and low which we can manually compare
if (isValid(arr, N, K, high)) {
return high;
} else {
return low;
}
}
// Driver code
public static void Main(String[] args)
{
// Defining input
int []arr = { 15, 20, 8, 17 };
int N = 4;
int K = 7;
// Calling the function
Console.WriteLine(maxHeight(arr, N, K));
}
}
// This code is contributed by shikhasingrajput
Javascript
15
时间复杂度: O(logN)
辅助空间: O(1)