二分查找

"learning……"

Posted by Ryan on January 6, 2025

两个囚犯从监狱里往外看,一个看到了泥土,一个看到了星星……

重点

二分查找的核心是通过减半缩减查找的次数,最主要的思想应用在普通二分查找快排分割旋转数组。难点在于其灵活的变形。

算法描述

二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为 O(n) 的数组,二分查找的时间复杂度为 O(log n)。

非递归:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <vector>

using namespace std;

// 二分查找函数
int binarySearch(const vector<int>& arr, int target) {
    int left = 0;
    int 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;
        }
    }

    // 如果没有找到目标值,返回 -1
    return -1;
}

int main() {
    vector<int> arr = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};  // 已排序数组
    int target = 7;

    int result = binarySearch(arr, target);

    if (result != -1) {
        cout << "元素 " << target << " 的索引是: " << result << endl;
    } else {
        cout << "元素 " << target << " 不在数组中。" << endl;
    }

    return 0;
}

递归实现的二分查找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int binarySearch(const vector<int>& nums,int left,int right,int target)
{
    if(left>right) return -1;
    int mid=(left+right)/2;
    if(nums[mid]==target)
    {
        return mid;
    }
    else if(nums[mid]>target)
    {
        return binarySearch(nums,left,mid-1,target);
    }
    else
    {
        return binarySearch(nums,mid+1,right,target);
    }
}

查找区间

leetcode.34.在排序数组中查找元素的第一个和最后一个位置:给定一个增序的整数数组和一个值,查找该值第一次和最后一次出现的位置。

二分查找变形(找到目标值之后向右或者向左重新二分查找):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int>result={-1,-1};
        return{binarys(nums,0,nums.size()-1,target,false),binarys(nums,0,nums.size()-1,target,true)};

    }
    int binarys(vector<int>& nums,int left,int right,int target,bool index)
    {
        int result=-1;
        while(left<=right)
        {
            int mid=(left+right)/2;
            if(nums[mid]==target)
            {
                result=mid;
                if(index)
                {
                    left=mid+1;
                }
                else
                {
                    right=mid-1;
                }
            }
            else if(nums[mid]>target)
            {
                right=mid-1;
            }
            else
            {
                left=mid+1;
            }
        }
        return result;
    }
};

旋转数组查数字

leecode.81.搜索旋转排序数组II:一个原本增序的数组被首尾相连后按某个位置断开。给定一个值,判断这个值是否存在于这个为旋转数组中。
题解:即使数组被旋转过,我们仍然可以利用这个数组的递增性,使用二分查找。对于当前的中点,如果它指向的值小于等于右端,那么说明右区间是排好序的;反之,那么说明左区间是排好序的。如果目标值位于排好序的区间内,我们可以对这个区间继续二分查找;反之,我们对于另一半区间继续二分查找。注意,因为数组存在重复数字,如果中点和左端的数字相同,我们并不能确定是左区间全部相同,还是右区间完全相同。在这种情况下,我们可以简单地将左端点右移一位,然后继续进行二分查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        while(left<=right)
        {
            int mid=(left+right)/2;
            if(nums[mid]==target) return true;

            if(nums[left]==nums[mid]) left++;//判断不出哪边有序
            else if(nums[mid]<=nums[right])
            {//右区间增序
                if (target > nums[mid] && target <= nums[right]) 
                {
                    left = mid + 1;
                } 
                else 
                {
                    right = mid - 1;
                }
            }
            else
            {
                if (target < nums[mid] && target >= nums[left]) 
                {
                    right = mid - 1;
                } 
                else 
                {
                    left = mid + 1;
                }
            }
        }
        return false;
    }
};

练习

154.寻找旋转排序数组中的最小值 II。
540.有序数组中的单一元素。
4.寻找两个正序数组的中位数。