二分查找小结

​ 最近和浩博讨论了下几道关于二分查找的题目。正好做一下总结,方便下一次能够快速手写出二分的算法。

​ 其实,二分查找,可以用下图简单表示流程。

  1. 最基本的二分查找:在一个递增的序列查找某个值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int len = nums.size();
    int low = 0, high = len - 1;
    int mid;
    while (low <= high)
    {
    mid = low + (high - low) / 2;
    if (nums[mid] == target)
    return true;
    else if (nums[mid] > target)
    high = mid - 1;
    else
    low = mid + 1;
    }
    return false;

  2. LeetCode 34. Search for a Range

    Given an array of integers sorted in ascending order, find the starting and ending position of a given target value.

    Your algorithm’s runtime complexity must be in the order of O(log n).

    If the target is not found in the array, return [-1, -1].

    For example,
    Given [5, 7, 7, 8, 8, 10] and target value 8,
    return [3, 4].

    ​ 找出相同元素的上界和下界索引。

    ​ 跟基本二分相比,就是要改变判断是否找到了的条件,不再是简单的a[mid] == target 就可以搞定的了。所以就手写了lowMatch 和 highMatch两个辅助函数来判断是否找到要找的上界和下界。以及思考如果这一回没找到接下来应该向左走还是向右走的问题。

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    class Solution {
    public:
    vector<int> searchRange(vector<int> &nums, int target)
    {
    vector<int> ans;
    int low = 0, high = nums.size() - 1;
    int mid;
    int ans1 = -1, ans2 = -1;
    while (low <= high)//找最低
    {
    mid = (low + high) / 2;
    if (lowMatch(nums, target, mid))
    {
    ans1 = mid;
    break;
    } else if (nums[mid] < target)
    low = mid + 1;
    else// nums[mid]>=target
    high = mid - 1;//即使相等,也一定在左边找
    }

    low = 0, high = nums.size() - 1;

    while (low <= high)//找最高
    {
    mid = (low + high) / 2;
    if (highMatch(nums, target, mid))
    {
    ans2 = mid;
    break;
    } else if (nums[mid] <= target)//由上一个条件,即使跟target相等也不符合了
    low = mid + 1;
    else //nums[mid]>target
    high = mid - 1;//mid有可能是解
    }

    ans.push_back(ans1);
    ans.push_back(ans2);
    return ans;
    }

    bool lowMatch(vector<int> &nums, int target, int i)
    {
    if (nums[i] != target)
    return false;
    if (i == 0)//第0个肯定是最左边的
    return true;
    else if (nums[i] != nums[i - 1])
    return true;
    return false;
    }

    bool highMatch(vector<int> &nums, int target, int i)
    {
    if (nums[i] != target)
    return false;
    if (i == nums.size() - 1)//最后一个肯定是最右边的
    return true;
    else if (nums[i] != nums[i + 1])
    return true;
    return false;
    }
    };

    ​ 其实这道题用STL模板库的lower_bound和upper_bound会更加简洁,但也少了锻炼自己手写二分的能力。(一开始就是用STL做的0-0)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Solution {
    public:
    vector<int> searchRange(vector<int>& nums, int target) {
    vector<int>::iterator low, up;
    low = lower_bound(nums.begin(), nums.end(), target);
    up = upper_bound(nums.begin(), nums.end(), target);
    vector<int> ans;

    if (!binary_search(nums.begin(),nums.end(),target))
    {
    ans.push_back(-1);
    ans.push_back(-1);
    } else
    {
    ans.push_back(low - nums.begin());
    ans.push_back(up - nums.begin() - 1);
    }
    return ans;
    }
    };
  3. LeetCode 153. Find Minimum in Rotated Sorted Array

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

    (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

    Find the minimum element.

    You may assume no duplicate exists in the array.

    ​ 一个本来有序的数组旋转了一下,变得整体无序,部分有序了。在这样的一个旋转后的数组里查找最小值。

    ​ 本质上还是可以用二分来做。就是在思考判断是否找到isFind以及向左走还是向右走的时候需要再加以修改。

    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
    class Solution {
    public:
    int findMin(vector<int>& rotateArray) {
    if (!rotateArray.size())
    return 0;
    if (rotateArray.size() == 1)
    return rotateArray[0];
    int len = rotateArray.size();

    if (rotateArray[0] < rotateArray[len - 1])//没有旋转
    return rotateArray[0];

    int low = 0, high = len - 1;
    int mid;
    while (low <= high)
    {
    mid = (low + high) / 2;
    if (isFind(rotateArray, mid))
    return rotateArray[mid];
    else if (rotateArray[mid] >= rotateArray[0])
    low = mid + 1;
    else
    high = mid - 1;
    }

    return 0;
    }

    bool isFind(vector<int> nums, int index)
    {
    if (index == nums.size() - 1)
    return nums[index] < nums[index - 1];
    else if (index == 0)
    return nums[index] < nums[nums.size() - 1];
    else
    return nums[index] < nums[index + 1] && nums[index] < nums[index - 1];
    }
    };
  4. LeetCode 154. Find Minimum in Rotated Sorted Array II

    Follow up for “Find Minimum in Rotated Sorted Array”:
    What if duplicates are allowed?

    Would this affect the run-time complexity? How and why?

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

    (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

    Find the minimum element.

    The array may contain duplicates.

    ​ 相比上一道题就是只加了一个条件:允许重复值。但是这时候我就按照我自己的二分查找思路以及找不到如何做了。因为在a[mid] == a[0] 的时候你可以往左走,也可以往右走。根本二分不了。看了解答才发现可以用high = high - 1 来缩小查找的范围,把二分log(n)变成了O(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
    class Solution {
    public:
    int findMin(vector<int> &array)
    {
    int low = 0;
    int high = array.size() - 1;
    int mid;
    while (low < high)
    {
    mid = low + (high - low) / 2;
    if (array[mid] > array[high])//最小值一定在mid右边
    {
    low = mid + 1;
    } else if (array[mid] == array[high])//最小值有可能在mid左边,也有可能在右边
    {
    high = high - 1;//去掉重复的,缩减范围
    } else//array[mid]<array[high] => mid右边是单调,mid是右边最小得数,也可能是左边最小的数,所以high = mid,而非high = mid-1
    {
    high = mid;
    }
    }
    return array[high];
    }
    };

    ​ 但是后来我又用最笨的O(n)查找的算法提交了以下发,发现耗时更短。上面的是12ms,用下面的线性查找反而是6ms。性能提升了一倍。所以,可能是有重复还不如不用二分,因为最小值可能在mid左边也有可能在右边,并不能缩减范围,用线性查找吧。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution {
    public:
    int findMin(vector<int> &array)
    {
    int minNum = INT32_MAX;
    for (auto i:array)
    {
    minNum = min(minNum, i);
    }
    return minNum;
    }
    };

    完!