二分查找

掌握二分查找的变种、优化问题和搜索空间。学习在排序数组之外应用二分查找解决复杂的竞赛编程问题。

1. 经典二分查找

二分查找的基础:在排序数组中以 O(log n) 时间复杂度查找元素。

基础二分查找实现

#include <iostream>
#include <vector>
using namespace std;

// Classic binary search
int binarySearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return -1; // Not found
}

// Lower bound: first position where element >= target
int lowerBound(vector<int>& arr, int target) {
    int left = 0, right = arr.size();
    
    while (left < right) {
        int mid = left + (right - left) / 2;
        
        if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return left;
}

// Upper bound: first position where element > target
int upperBound(vector<int>& arr, int target) {
    int left = 0, right = arr.size();
    
    while (left < right) {
        int mid = left + (right - left) / 2;
        
        if (arr[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return left;
}

int main() {
    vector<int> arr = {1, 2, 2, 2, 3, 4, 5, 6, 7, 8};
    
    cout << "Array: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    int target = 2;
    cout << "Target: " << target << endl;
    cout << "Binary search result: " << binarySearch(arr, target) << endl;
    cout << "Lower bound: " << lowerBound(arr, target) << endl;
    cout << "Upper bound: " << upperBound(arr, target) << endl;
    cout << "Count of target: " << (upperBound(arr, target) - lowerBound(arr, target)) << endl;
    
    return 0;
}

2. 二分答案技术

在答案空间上进行二分查找,找到优化问题的最优解。

二分答案示例:最小化最大子数组和

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// Check if we can split array into k subarrays with max sum <= maxSum
bool canSplit(vector<int>& arr, int k, long long maxSum) {
    int subarrays = 1;
    long long currentSum = 0;
    
    for (int num : arr) {
        if (num > maxSum) return false;
        
        if (currentSum + num <= maxSum) {
            currentSum += num;
        } else {
            subarrays++;
            currentSum = num;
            if (subarrays > k) return false;
        }
    }
    
    return true;
}

long long splitArray(vector<int>& arr, int k) {
    long long left = *max_element(arr.begin(), arr.end());
    long long right = 0;
    
    for (int num : arr) {
        right += num;
    }
    
    while (left < right) {
        long long mid = left + (right - left) / 2;
        
        if (canSplit(arr, k, mid)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    
    return left;
}

int main() {
    vector<int> arr = {7, 2, 5, 10, 8};
    int k = 2;
    
    cout << "Array: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    cout << "Split into " << k << " subarrays" << endl;
    cout << "Minimum possible maximum sum: " << splitArray(arr, k) << endl;
    
    return 0;
}

3. 三分查找

三分查找用于在单峰函数中以 O(log n) 复杂度找到极值。

三分查找实现

#include <iostream>
#include <cmath>
using namespace std;

// Example function: f(x) = -(x-3)^2 + 10 (has maximum at x=3)
double f(double x) {
    return -(x - 3) * (x - 3) + 10;
}

// Ternary search for maximum
double ternarySearchMax(double left, double right, double eps = 1e-9) {
    while (right - left > eps) {
        double m1 = left + (right - left) / 3;
        double m2 = right - (right - left) / 3;
        
        if (f(m1) < f(m2)) {
            left = m1;
        } else {
            right = m2;
        }
    }
    
    return (left + right) / 2;
}

// Ternary search for minimum
double ternarySearchMin(double left, double right, double eps = 1e-9) {
    while (right - left > eps) {
        double m1 = left + (right - left) / 3;
        double m2 = right - (right - left) / 3;
        
        if (f(m1) > f(m2)) {
            left = m1;
        } else {
            right = m2;
        }
    }
    
    return (left + right) / 2;
}

// Discrete ternary search
int ternarySearchMaxDiscrete(int left, int right) {
    while (right - left > 2) {
        int m1 = left + (right - left) / 3;
        int m2 = right - (right - left) / 3;
        
        if (f(m1) < f(m2)) {
            left = m1;
        } else {
            right = m2;
        }
    }
    
    // Check remaining candidates
    double maxVal = f(left);
    int result = left;
    
    for (int i = left + 1; i <= right; i++) {
        if (f(i) > maxVal) {
            maxVal = f(i);
            result = i;
        }
    }
    
    return result;
}

int main() {
    double left = 0, right = 6;
    
    cout << "Function: f(x) = -(x-3)^2 + 10" << endl;
    cout << "Search range: [" << left << ", " << right << "]" << endl;
    
    double maxPoint = ternarySearchMax(left, right);
    cout << "Maximum at x = " << maxPoint << endl;
    cout << "f(" << maxPoint << ") = " << f(maxPoint) << endl;
    
    // Discrete version
    int discreteMax = ternarySearchMaxDiscrete(0, 6);
    cout << "Discrete maximum at x = " << discreteMax << endl;
    cout << "f(" << discreteMax << ") = " << f(discreteMax) << endl;
    
    return 0;
}

总结

  • 经典二分查找:在排序数组中以 O(log n) 复杂度查找元素
  • 下界/上界:找到插入点并计算出现次数
  • 二分答案:在答案空间上搜索优化问题的解
  • 三分查找:在单峰函数中找到极值
  • 应用:优化、区间查询和搜索问题