二分问题总结
二分问题可以按循环条件总结为两个模板:
循环条件为(left <= right):
while(left <= right) { int mid = (right + left) / 2; if(nums[mid] == target) return mid; else if (nums[mid] < target) left = mid + 1; // 注意 else if (nums[mid] > target) right = mid - 1; // 注意 } return -1;
如果是找小于等于target的第一个数,则返回right;
如果是找大于target的数,则返回left;
这样的题目有:
69. x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
class Solution { public: int mySqrt(int x) { int l = 0, r = x; while(l <= r){ int m = l + (r-l)/2; long long mm = m; mm*=mm; // 不能直接用mm= m*m; if(mm == x) return m; else if(mm >x) r = m-1; else l = m+1; } return r; } };
744. 寻找比目标字母大的最小字母
给定一个只包含小写字母的有序数组letters 和一个目标字母 target,寻找有序数组里面比目标字母大的最小字母。
数组里字母的顺序是循环的。举个例子,如果目标字母target = 'z' 并且有序数组为 letters = ['a', 'b'],则答案返回 'a'。
class Solution { public: char nextGreatestLetter(vector<char>& letters, char target) { int n = letters.size(); int l = 0, r = letters.size() -1; while(l <= r){ int m = l + (r-l)/2; if(letters[m] <= target) l = m+1;//因为是找大于; else r = m-1; } return l<n? letters[l]: letters[0]; } };
第二种模板是排除法,有向左排除和向右排除:
int mid = left + (right - left) / 2; if (check(mid)) { // 下一轮搜索区间是 [left, mid] right = mid; } else { left = mid + 1; }
int mid = left + (right - left + 1) / 2; if (check(mid)) { // 下一轮搜索区间是 [left, mid - 1] right = mid - 1; } else { left = mid; }
540. 有序数组中的单一元素
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
class Solution { public: int singleNonDuplicate(vector<int>& nums) { int n= nums.size(); int l = 0, r = n-1; // r = n 则不行 while( l < r){ int m = l + (r-l)/2; if(m&1) --m; // 让m始终指向偶数个 //元素在右边 if(nums[m] == nums[m+1]) l = m+2; //元素在左边或者本身; else r = m; } return nums[l]; } };
278. 第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。 调用 isBadVersion(3) -> false 调用 isBadVersion(5) -> true 调用 isBadVersion(4) -> true 所以,4 是第一个错误的版本。
// Forward declaration of isBadVersion API. bool isBadVersion(int version); class Solution { public: int firstBadVersion(int n) { int l = 1, r = n; while(l < r){ int m = l + (r-l)/2; if(isBadVersion(m)) r = m; else l = m+1; } return l; } };
153. 寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
class Solution { public: int findMin(vector<int>& nums) { int n = nums.size(); int l = 0, r = n-1; while(l < r){ int m = l + (r - l)/2; if(nums[m] < nums[r]) r = m; else l = m+1; } return nums[l]; } };
34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { int n = nums.size(); if(n == 0) return {-1,-1}; int l = 0,r = n-1; vector<int> res(2); while( l < r){ int m = l + (r-l)/2; if(nums[m] >= target) r = m; else l = m+1; } if(nums[l] == target) res[0] = l; else res[0] = -1; l = 0; r = n-1; while( l < r){ int m = l + (r-l+1)/2; // 注意 if(nums[m] <= target) l = m; else r = m-1; } if(nums[r] == target) res[1] = r; else res[1] = -1; return res; } };
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
class Solution { public: int searchInsert(vector<int>& nums, int target) { int n = nums.size(); int l = 0, r = n;// 注意,因为插入位置可能是最后一个位置; while( l < r){ int m = l +(r-l)/2; if(nums[m] >= target) r = m; else l = m+1; } return l; } };
参考资料: