1.二分查找

1.二分查找


1.1二分查找定义

有序数组里进行查找,数组中无重复元素

  • 关键与否在于对区间的定义
    区间的定义就是不变量。 要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是 循环不变量 规则。

1.2 二分法的两种写法

1.2.1 第一种

int halfsearch(vector<int>& nums, int target, int left, int right) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target < nums[mid]) {
                right = mid - 1; // target 在左区间,所以[left, middle - 1]
            } else if (target > nums[mid]) {
                left = mid + 1; // target 在右区间,所以[middle + 1, right]
            } else {
                return mid;
            }
        }
        return -1; 
    }

左闭右闭
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是 [left, right] (这个很重要非常重要)

区间的定义这就决定了二分法的代码应该如何写,因为 定义target在[left, right]区间 ,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

1.2.2 第二种

int halfsearch(vector<int>& nums, int target, int left, int right) {
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (target < nums[mid]) {
                right = mid; //target 在左区间,在[left, middle)中
            } else if (target > nums[mid]) {
                left = mid + 1; // target 在右区间,在[middle + 1, right)中
            } else {
                return mid;
            }
        }
        return -1; 
    }

如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。

有如下两点:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

1.3 leetcode例题

1.3.1 在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

原题

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
         int leftBorder = getLeftIndex(nums, target);
        int rightBorder =  getRightIndex(nums, target);
        if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
        return {-1, -1};
    }
private:
    int getRightIndex(vector<int>& nums, int target) { //获取右边界
        int right = nums.size() - 1;
        int left = 0;
        int rightIndex = -1;
        while (left<=right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] < target) {
                left = middle + 1;
            } else if (nums[middle] > target) {
                right = middle - 1;
            } else {
                left = middle + 1;
                rightIndex = left;
            }
        }
        return rightIndex;
    }
    int getLeftIndex(vector<int>& nums, int target) { //获取左边界
        int right = nums.size() - 1;
        int left = 0;
        int leftIndex = -1;
        while (left<=right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] < target) {
                left = middle + 1;
            } else if (nums[middle] > target) {
                right = middle - 1;
            } else {
                right = middle - 1;
                leftIndex = right;
            }
        }
         return leftIndex;
    }
};

思路就是通过二分法找到两个左右边界
时间复杂度为O(log n)


1.3.2 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

原题

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = (int)nums.size();
        if (!n) {
            return -1;
        }
        if (n == 1) {
            return nums[0] == target ? 0 : -1;
        }

        int mid = findHalf(nums);
        if(target >= nums[0]){ //找到target在哪个区间
            return halfsearch(nums,target, 0, mid);
        } else { 
            return halfsearch(nums, target, mid+1, nums.size()-1);
        }
    }
private:
    int findHalf(vector<int>& nums) { //找到分叉点
        int l = 0;
        int r = nums.size() - 1;
        if(nums[l] <= nums[r]) 
            return r;
        while(l < r){
            int mid = l + (r - l) / 2;
            if(nums[l] < nums[mid]) 
                l = mid;
            else 
                r = mid;
        }
        return l;
    }
    int halfsearch(vector<int>& nums, int target, int left, int right) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target < nums[mid]) {
                right = mid - 1;
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1; 
    }
};

思路:利用二分查找,查找到target之后,可能它的左边和右边还可能有target,所以还要继续查找
时间复杂度:O(logN) 空间复杂度:O(1)


1.3.3 搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

- 每行中的整数从左到右按升序排列。
- 每行的第一个整数大于前一行的最后一个整数。

原题

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int i = 0;
        for (i = 0; i < matrix.size(); i++) {
            if (target < matrix[i][0]) {
                break;
            }
        }
        if (i!=0)
            i--;
        return BubinarySearch(matrix[i], target, 0, matrix[i].size()-1);
    }
private:
    bool BubinarySearch(vector<int> nums, int target, int left, int right) {
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target){
                return true;
            } else if (target < nums[mid]) {
                right = mid - 1;

            } else {
                left = mid + 1;
            }
        }
        return false;
    }
};

思路:找到target在的行,对行进行二分查找


1.4 感悟

写二分查找时要注意 区间的不变性 ,明白每个等于号的意义。
像左闭右闭的情况 left == right在区间 [left,right] 是有意义的。所以while(left <= right)
而像左闭右开的情况 left == right在区间 [left,right) 是没有意义的所以while(left < right)


sigma

posted @ 2023-02-02 14:07  RoMGK  阅读(29)  评论(0)    收藏  举报