二分法 (配列内に繰り返しの数値が存在しない場合と、繰り返しの数値が存在する 2 つのケース)

1. 繰り返される数値のない非減少配列

[3 5 7 9] 配列で 9 を見つけます。

nums[mid] > target の場合、ターゲットは左半分にあります (j = mid - 1)。

nums[mid] < target の場合、ターゲットは右半分にあり、i = mid + 1;

nums[mid] = target の場合、ターゲットが見つかったことを示し、直接戻ります。

質問: なぜ下に左 <= 右が必要なのでしょうか?

他のアルゴリズムでは、  「left <= right」が発生し、「=」を使用するかどうかわからないため、残りの要素をループする必要があるかどうかを自問してください。

//二分查找
int BinarySearch(vector<int> &nums, int target){
    int left = 0, right = nums.size() - 1;
    while(left <= right){
        int mid = (left + right) >> 1;// 或者 left + (right - left) /2;
        if(nums[mid] == target)
            return mid;
        if(nums[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return -1;
}


2. 数値が繰り返される非減少配列

lower_bound関数と同様に、指定された要素以上の配列の二分探索方法を見つけます

1. arr[mid] >= xの場合、midに対応する要素が最終結果となる可能性があり、更新結果=mid、r=mid - 1(結果にはmidが格納されているため、arr[mid]を考慮する必要はありません) )、if 変更されていない場合、最終結果は arr[mid] となり、それ以外の場合は変更された値になります (変更されたとは、mid の左側に x 以上の値があることを示します)。

2. arr[mid] < x の場合、x は右半分にあり、l = mid + 1;

#include <iostream>
using namespace std;

int binarySearch(int arr[], int n, int x) {
    int l = 0, r = n - 1;
    int result = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (arr[mid] >= x) {
            result = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    return result;
}

int main() {
    int arr[] = { 2, 3, 4, 4, 4, 10, 10, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int x = 10;
    int result = binarySearch(arr, n, x);
    if (result == -1)
        cout << "Element not present in array";
    else
        cout << "First element greater than or equal to " << x << " is " << arr[result];
    return 0;
}

upper_bound関数と同様に、配列内で指定された要素より大きい要素を見つけるための二分検索

1. arr[mid] > x の場合、mid に対応する要素が最終結果となる可能性があり、更新結果 = Mid、r = Mid - 1 (mid は結果に格納されているため、arr[mid] を考慮する必要はありません) 、変更されていない場合、最終結果は arr[mid] ですが、それ以外の場合は変更された値になります (変更されたとは、mid の左側に x 以上の値があることを示します)。

2. arr[mid] <= x の場合、x は右半分にあり、l = mid + 1;

注: [0, 2, 2] が 2 より大きい最初の値を見つける場合、戻り値は -1 であり、見つからないことを意味します。 

#include <iostream>
using namespace std;

int binarySearch(int arr[], int n, int x) {
    int l = 0, r = n - 1;
    int result = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (arr[mid] > x) {
            result = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    return result;
}

int main() {
    int arr[] = { 2, 3, 4, 4, 4, 10, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int x = 4;
    int result = binarySearch(arr, n, x);
    if (result == -1)
        cout << "Element not present in array";
    else
        cout << "First element greater than " << x << " is " << arr[result];
    return 0;
}


3. 数値が繰り返される非減少配列 (私が書いたのでわかりやすいですが、特別な事情を考慮する必要があります)

配列 [5 7 7 8 8 8 9 10] について、8 の左境界と右境界 (最小添字と最大添字) を見つけます。

要素が配列内に存在しない場合は、時間内に返す必要があるので、特に注意してください。

1. 左の境界線を見つけます。

nums[mid] > target の場合、ターゲットは左半分にあります (j = mid - 1)。

nums[mid] < target の場合、ターゲットは右半分にあり、i = mid + 1;

nums[mid] = target の場合、左側の境界は区間 [i,mid]、j =mid 内にあります。

2. 適切な境界を見つけます。

nums[mid] > target の場合、ターゲットは左半分にあります (j = mid - 1)。

nums[mid] < target の場合、ターゲットは右半分にあり、i = mid + 1;

nums[mid] = target の場合、右側の境界は区間 [mid, j]、i = mid 内にあります。

境界のある特殊なケースが 2 つあります。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.size() == 0)return 0;
        return binary_search(nums, target);
    }
    int binary_search(vector<int>& nums, int target)
    {
        // 先找左边界
        int i = 0, j = nums.size() - 1;
        int m = 0;
        while(i < j)
        {
            m = i + (j-i)/2;
            if(nums[m] > target)j = m - 1;
            else if(nums[m] < target)i = m + 1;
            else if (nums[m] == target)j = m;
            // cout<< "ertg"<<endl;
        }
        int left = i;// i == j == 左边界

        if(nums[left] != target)return 0;// 不存在target直接返回,一定要特别注意

        // 再找右边界
        m = 0;
        i = 0; j = nums.size() - 1;
        while(i < j)
        {
            m = i + (j-i)/2;
            if(nums[m] > target)j = m - 1;
            else if(nums[m] < target)i = m + 1;
            else if (nums[m] == target)i = m;
            
            // 处理两种特殊情况
            if(nums[i] == target && j - i == 1)
            {
                if(nums[j] != target)// 第一种
                {
                    j = i;// j == i == m
                    break;
                }
                else i++;// 第二种
            }
            // cout<< "111"<<endl;
        }
        int right = i;// i == j == 右边界

        // cout<< right << " " << left<<endl;
        return right - left + 1;
    }
};

おすすめ

転載: blog.csdn.net/qq_41709234/article/details/131011060