算法(二)二分法基础讲解

0. 基本思想

利用二分法解决问题的前提是数据是有序的,如果数组无序,需要提前排好序。编写代码的过程中需要注意以下5点:

  1. while循环结束条件 -> low <= high or low < high,一般情况下对于查找数组中某个元素出现位置问题 -> low < high;其他情况 -> low <= high。
  2. 循环中mid的取值 -> mid = (low + high) / 2 or mid = (low + high) / 2 + 1,这个需要视情况而定。
  3. 缩减范围时high的取值 -> high = mid - 1 or high = mid。
  4. 缩减范围时low的取值 -> low = mid + 1 or low = mid。
  5. return时返回low还是high -> 这个往往取决于第1个条件。

1. 查找元素位置

上述5点逐条解析可得:

  1. while循环结束条件 -> 由于是查找问题,需要low < high。
  2. 循环中mid的取值 -> mid = (low + high) / 2。
  3. 缩减范围时high的取值 -> 当nums[mid] > target时,high = mid - 1。
  4. 缩减范围时low的取值 -> 当nums[mid] < target时,low = mid + 1。
  5. return时返回值 -> 查找到就返回mid,否则返回-1。

示例代码如下所示:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if (nums.empty()){
			return -1;
        }
        if (nums.size() == 1 && nums[0] == target){
			return 0;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            if(nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return -1;
    }
};

递归方法如下:

class Solution {
public:
	int search_recur(vector<int>& nums, int target, int lo, int hi) {
		if (lo == hi && nums[lo] == target) {
			return lo;
		}
		if (lo < hi) {
			int mid = (lo + hi) / 2;
			if (nums[mid] == target) {
				return mid;
			} else if (nums[mid] < target) {
				return search_recur(nums, target, mid + 1, hi);
			} else {
				return search_recur(nums, target, lo, mid - 1);
			}
		}

		return -1;
	}
	
    int search(vector<int>& nums, int target) {
        if (nums.empty()) {
			return -1;
		}
		int lo = 0;
		int hi = nums.size() - 1;
		return search_recur(nums, lo, hi);
    }
};

2. 查找第一个大于等于某元素的数组位置

上述5点逐条解析可得:

  1. while循环结束条件 -> 由于不是查找出现元素位置,因而结束条件为low <= high。
  2. 循环中mid的取值 -> 查找第一个位置,mid的位置要尽量靠前,因而mid = (low + high) / 2。
  3. 缩减范围时high的取值 -> 当nums[mid] == target时,需要让high往前跳,因为如果让low往后跳,则容易错过第一个大于等于target的位置,因为数组有可能重复,这时需要令high = mid - 1,因为如果令high = mid,则会陷入死循环(demo case为nums=[0, 1], target=0);当nums[mid] > target时,high = mid - 1。
  4. 缩减范围时low的取值 -> 当nums[mid] < target时,需要令low = mid + 1,因为low的作用是找到第一个大于等于某元素的数组位置,因而low需要跳出不符合要求的区域。
  5. return时返回low还是high -> 因为每次缩减范围时,high会跳入不符合要求区域,low会跳入符合要求区域,因而需要返回low。

示例代码如下:

class Solution {
public:
    int searchFirstLargerEqualLocation(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        
        return lo;
    }
};

3. 查找最后一个小于等于某元素的数组位置

上述5点逐条解析可得:

  1. while循环结束条件 -> 由于不是查找出现元素位置,因而结束条件为low <= high。
  2. 循环中mid的取值 -> mid = (low + high) / 2
  3. 缩减范围时low的取值 -> 当nums[mid] == target时,需要让low向后跳,因为如果让high向前跳,则会出现错过最后一个小于等于target的位置(demo case为nums = [0, 1, 1, 2], target = 1),low的具体取值为mid + 1,这时因为low需要跳出符合要求的范围,让high发挥找到具体数组位置的作用;当nums[mid] < target时,需要让low = mid + 1,理由同上。
  4. 缩减范围时high的取值 -> 当nums[mid] > target时,需要让high = mid - 1,因为high需要跳入符合要求的范围,作用为找到最后一个小于等于某元素的数组位置
  5. return时返回low还是high -> high的作用为找到目标位置,因而需要返回high。

示例代码如下:

class Solution {
public:
    int searchFirstLargerEqualLocation(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] <= target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        
        return hi;
    }
};

4. 查找最后一个小于某元素的数组位置

查找最后一个小于等于某元素的数组位置,相当于第一个大于等于某元素的数组位置-1,所以可以发现,如下示例代码和<2. 查找第一个大于等于某元素的数组位置>的差距只有返回值的差距,因为在跳出while循环时lo一定等于hi+1。

class Solution {
public:
    int searchFirstLargerEqualLocation(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        
        return hi;
    }
};

5. 查找第一个大于某元素的数组位置

查找第一个大于某元素的数组位置,相当于最后一个小于等于某元素的数组位置+1,所以可以发现,如下示例代码和<3. 查找最后一个小于等于某元素的数组位置>的差距只有返回值的差距,因为在跳出while循环时lo一定等于hi+1。

class Solution {
public:
    int searchFirstLargerLocation(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] <= target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        
        return lo;
    }
};

6. 数组有重复,查找某元素出现的第一个位置

思路1

上述5点逐条解析可得:

  1. while循环结束条件 -> 目标为查找元素出现位置,因而结束条件为low < high。
  2. 循环中mid的取值 -> 查找第一个位置,mid的位置要尽量靠前,因而mid = (low + high) / 2。
  3. 缩减范围时high的取值 -> 当target == nums[mid]时,必须要让high向前跳,而不是low向后跳,因为如果令low向后跳,则有可能导致错过target出现的第一个位置(数组升序);在此前提下,当nums[mid] >= target时,high 需要 = mid,因为如果令high = mid - 1,当low = 0&high = 1&nums[mid] > target时,high会 = -1从而导致数组越界。
  4. 缩减范围时low的取值 -> 当nums[mid] < target时,low到mid之间没有符合要求的元素,因而令low = mid + 1。
  5. return时返回low还是high -> 跳出循环时low = high,因而返回low和high都无所谓,但返回前需要判断nums[low]或者nums[high]是否为target,但返回high更容易理解,如果不等于则需要返回-1。

示例代码如下:

class Solution {
public:
    int searchFirstLocation(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }

		if (nums[hi] == target) {
			return hi;
		}
		
        return -1;
    }
};

思路2

仔细想想,这道题和<2. 查找第一个大于等于某元素的数组位置>一摸一样,因而可以完全套用。

7. 数组有重复,查找某元素出现的最后一个位置

思路1

上述5点逐条解析可得:

  1. while循环结束条件 -> 查找元素出现位置,因而结束条件为low < high。
  2. 循环中mid的取值 -> 查找最后一个位置,mid的位置要尽量靠后,因而mid = (low + high) / 2 + 1。
  3. 缩减范围时low的取值 -> 当nums[mid] == target,low需要往后跳,否则high往前跳则会导致漏掉nums中等于target的位置,这时low需要= mid,如果= mid + 1,则有可能错过mid这个位置;当nums[mid] < target时,low需要= mid,否则有可能出现数组越界的情况(demo case为nums=[1,3,4,5], target=5)。
  4. 缩减范围时high的取值 -> 当nums[mid] > target时,high需要= mid - 1。
  5. return时返回low还是high -> 跳出循环时low = high,因而返回low和high都无所谓,但返回前需要判断nums[low]或者nums[high]是否为target,但返回low更容易理解,如果不等于则需要返回-1。

示例代码如下:

class Solution {
public:
    int searchLastLocation(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2 + 1; // 破圈
            if (nums[mid] <= target) {
                lo = mid;
            } else {
                hi = mid - 1;
            }
        }

		if (nums[lo] == target) {
			return lo;
		}
		
        return -1;
    }
};

思路2

仔细想想,这道题和<3. 查找最后一个小于等于某元素的数组位置>一摸一样,因而可以完全套用。

查找等于某元素位置,如果没出现则插入到指定位置

思路1

上述5点逐条解析可得:

  1. while循环结束条件 -> 由于是查找后插入问题,一般当low == high如果没找到时,需要继续遍历直到找到插入位置,因而while循环结束的条件为low <= high。
  2. 循环中mid的取值 -> 如果mid = (low + high) / 2 + 1,则会出现high = low + 1且nums[high]依然大于target的情况,这样会出现死循环的情况(demo case为nums=[1,3,5,6], target=2),因而需要mid = (low + high) / 2。
  3. 缩减范围时high的取值 -> 这里high不能等于mid,因为会出现low = high = mid的死循环情况(demo case为nums=[1,3,5,6], target=2),因而需要high = mid - 1。
  4. 缩减范围时low的取值 -> 这里显而易见,因而如果nums[mid] < target,这证明low到mid之间的数据都是不符合要求的(包括mid本身),因而需要low = mid + 1。
  5. return时返回low还是high -> 循环结束时如果找到target,则low = high,这时返回谁都无所谓;如果没有找到target,则low = high + 1,因而需要返回low;综合两种情况,需要返回low。

示例代码如下所示:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        if(nums.empty()){
			return -1;
        }
        int lo = 0;
        int hi = nums.size() - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if(nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return lo;
    }
};

思路2

本题和<2. 查找第一个大于等于某元素的数组位置>基本一致,只不过返回值需要是<2. 查找第一个大于等于某元素的数组位置>的返回值。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值