必须了解的编程基础 -- 二分搜索小节

1. 二分搜索

1.1 leetcode35 搜索插入位置1

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5
输出: 2

示例 2:

输入: [1,3,5,6], 2
输出: 1

示例 3:

输入: [1,3,5,6], 7
输出: 4

示例 4:

输入: [1,3,5,6], 0
输出: 0

按照二分法思路列举示例归纳规律:[1,3,5,6]

  1. begin = 0, end = 3, mid = 1; target = 5, 恰好是nums[mid].
  2. target = 2, target < nums[mid] && target > nums[mid-1], 所以 返回 mid -1;
  3. 同样地,当 target > nums[mid] && target < nums[mid+1], 返回 mid + 1;
  4. target = 7, target > nums[mid], begin = mid + 1 = 2; mid = (2 + 3)/2 = 2;
    target > nums[mid]; begin = mid + 1 = 2 + 1 = 3, mid = (3 + 3)/2 = 3;
    target > nums[mid]; begin = mid + 1 = 4, 出现了越界情况, 此时应该返回mid+1。应该边界 mid = nums.size()-1 返回mid + 1;
  5. 同样地,对于左边界越界时,当mid = 0时,返回0;
class Solution {
     
public:
    int searchInsert(vector<int>& nums, int target) {
     
        int index = -1;
        int begin = 0, end = nums.size() - 1;
        while (index == -1) {
     
            int mid = (begin + end) >> 1;
            if (target == nums[mid]) index = mid;
            else if (target < nums[mid]) {
     
                if (mid == 0 || target > nums[mid-1]) {
     
                    index = mid;
                }
                end = mid - 1;
            }
            else if (target > nums[mid]){
     
                if (mid == nums.size()-1 || target < nums[mid+1]) {
     
                    index = mid + 1;
                }
                begin = mid + 1;
            }
        }
        return index;
    }
};

小节:二分查找的实质就是不断更新mid,然后来锁定要找的target,本题同样也是这样,不过不同的是本题还需要对mid周围的值施加限制:target只能是出现在mid的左/右两边,或者是出现在左右边界。

这个对mid周围值施加限制条件的思想,还会在下一题中应用的到。

1.2 Leetcode 34. 在排序数组中查找元素的第一个和最后一个位置2

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O ( l o g n ) O(log n) O(logn) 级别。
如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

分析:

  1. 这个光靠举例子是归纳规律还是有点难度的,需要分析一下;
  2. 如果光靠二分法能不能找到左右端点?显然不能,二分查找最短只能找到一个目标值的位置或者确认这个值不存在。
  3. 既然二分法可以找到一个目标值的位置,那么也就能找到两个端点,因为端点也是目标值。
  4. 从上面一题可以得知,二分查找法就是不断更新mid的值,从nums[mid]或者mid周围的值来搜索目标值的。
  5. 从[5,7,7,8,8,8,10]出发,如果mid指向最终间的那个8,即有 nums[mid] == target;由4可知,我们需要更新mid来查找端点,要么向左,要么向右去更新mid,又因为一次查找只能往一个方向移动,但是左右端点位于两个方向上,因此,需要对将左右两个端点的查找任务分开进行3
  6. 查找左端点:会有两个情况:[5,7,7,8,8,8,10] 和 [8,8,8,10], 当 nums[mid] == target 时, 如果mid == 0 || target > nums[mid-1], 说明此时的mid是左端点,否则说明左端点在mid左面,即要将mid向左移动,又因为mid是通过begin和end更新的,所以令end = mid - 1来更新mid;剩余的target < nums[mid]和 target > nums[mid] 情况和二分查找法相同3
  7. 查找右端点:同样会有两个情况:[5,7,7,8,8,8,10] 和 [7,8,8,8], 当 nums[mid] == target 时, 如果mid == nums.size()-1 || target < nums[mid+1], 说明此时的mid是右端点,否则说明右端点在mid右面,即要将mid向右移动,又因为mid是通过begin和end更新的,所以令 begin = mid + 1;剩余的target < nums[mid]和 target > nums[mid] 情况和二分查找法相同3
class Solution {
     
public:
    vector<int> searchRange(vector<int>& nums, int target) {
     
        vector<int> ans;
        ans.push_back(left_bound(nums, target));
        ans.push_back(right_bound(nums, target));
        return ans;
    }

    int left_bound(vector<int>& nums, int target) {
     
        int begin = 0;
        int end = nums.size()-1;

        while (begin <= end) {
     
            int mid = (begin+end)>>1;
            if (target == nums[mid]) {
     
                if (mid == 0 || target > nums[mid-1]) {
     
                    return mid;
                }
                end = mid - 1;
            }
            else if (target < nums[mid]) {
     
                end = mid - 1;
            }
            else if (target > nums[mid]) {
     
                begin = mid + 1;
            }
        }
        return -1;
    }

    int right_bound(vector<int>& nums, int target) {
     
        int begin = 0;
        int end = nums.size()-1;

        while (begin <= end) {
     
            int mid = (begin+end)>>1;
            if (target == nums[mid]) {
     
                if (mid == nums.size()-1 || target < nums[mid+1]) {
     
                    return mid;
                }
                begin = mid + 1;
            }
            else if (target < nums[mid]) {
     
                end = mid - 1;
            }
            else if (target > nums[mid]) {
     
                begin = mid + 1;
            }
        }

        return -1;
    }
};

1.3 LeetCode 33. 搜索旋转排序数组4

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

分析:同样还是需要思考+举例

  1. 旋转数组是个新概念,也一定有属于自己的特点。通过示例和举例,发现旋转数组的特点:如果区间[0, mid]和[mid, nums.size()-1]至少有一个是单调递增的。
  2. 如果对示例1使用二分查找,mid 会一直向左更新,不会找到0;原因在于[4,5,6,7]恰好是递增的。所以,现在问题变成,如何更新mid,使得程序能够找到traget = 0;
  3. 先结合1.旋转数组的特点,确定target属于[0, mid]和[mid, nums.size()-1]哪个区间。然后在所属区间上进行mid更新操作。
class Solution {
     
public:
    int search(vector<int>& nums, int target) {
     
        int begin = 0;
        int end = nums.size()-1;

        while (begin <= end) {
     
            int mid = (begin + end)>>1;
            if (target == nums[mid]) return mid;
            else if (nums[0] <= nums[mid]) {
     
                if (nums[begin] <= target && target <= nums[mid]) {
     
                    end = mid - 1;
                }
                else {
     
                    begin = mid + 1;
                }
            }
            else if (nums[mid] <= nums[end]) {
     
                if (nums[mid] <= target && target <= nums[end]) {
     
                    begin = mid + 1;
                }
                else {
     
                    end = mid - 1;
                }
            }
        }
        return -1;
    }
};

1.3 二分查找小节

  1. 二分查找的实质就是不断更新mid,然后来锁定要找的target; 而mid的更新是通过begin和end来决定的, begin和end的更新决定下一个mid值位于当前mid的左端还是右端。
  2. 1.1和1.2是通过对mid左右附近值的限定来实现target的查找;
  3. 1.3是通过对mid更新方向的选择来实现target的查找。

  1. https://leetcode-cn.com/problems/search-insert-position/ ↩︎

  2. https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/ ↩︎

  3. https://www.bilibili.com/video/BV1GW411Q77S?p=6 ↩︎ ↩︎ ↩︎

  4. https://leetcode-cn.com/problems/search-in-rotated-sorted-array/ ↩︎

你可能感兴趣的:(编程基础,二分法,leetcode,数据结构,算法)