基础题:704. 二分查找
思路
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
思路一:二分查找(左闭右闭)
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution {
public int search(int[] nums, int target) {
// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
}
思路二:二分查找(左闭右开)
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid;
}
return -1;
}
}
35. 搜索插入位置
思路
- 其余的过程和二分查找一致
- 为什么最后返回left? 因为退出while循环后,left > right,此时有两种情况: 一种是middle指向的值大于target(此时middle是第一个大于target的下标),此时right=middle-1导致了left大于right, 而target要插入的位置是middle的位置,即left,因为left和middle相等 另一种是middle指向的值小于target,此时left=middle+1导致了left大于right,而target要插入的位置是middle+1的位置,即left
代码实现(java)
class Solution {
public int searchInsert(int[] nums, int target) {
// 暴力方法
// int[] n = new int[nums.length + 1];
// for (int i = 0; i < nums.length; i++) {
// if (nums[i] == target) return i
// n[i] = nums[i];
// }
// n[n.length - 1] = target;
// Arrays.sort(n);
// int left = 0;
// int right = n.length - 1;
// while (left <= right) {
// int mid = left + (right - left) / 2;
// if (n[mid] == target) return mid;
// else if (n[mid] > target) right = mid - 1;
// else if (n[mid] < target) left = mid + 1;
// }
// return -1;
// 二分查找方法
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] > target) right = mid - 1;
else if (nums[mid] < target) left = mid + 1;
}
return left;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
思路
按照二分查找方法,先查左值;再查右值
- 左值
// 二分找左值
public int LeftbinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
if (left == nums.length || nums[left] != target) left = -1;
return left;
}
- 右值
// 二分找右值
public int RightbinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (right == -1 || nums[right] != target) right = -1;
return right;
}
实现代码(java)
class Solution {
int[] res = new int[2];
public int[] searchRange(int[] nums, int target) {
res[0] = LeftbinarySearch(nums, target);
res[1] = RightbinarySearch(nums, target);
return res;
}
// 二分找左值
public int LeftbinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
if (left == nums.length || nums[left] != target) left = -1;
return left;
}
// 二分找右值
public int RightbinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (right == -1 || nums[right] != target) right = -1;
return right;
}
}
69. x的平方根
思路
- x的平方根范围一定在 [1, x/2 + 1]中
- 按照二分查找,条件为:
- 如果mid < x / mid:说明mid偏小,则往大找,left = mid + 1
;
- 如果mid > x / mid:说明mid偏大,则往小找,right = mid - 1
;
- 找到了就直接返回mid
- 如果到最后循环过程没有找到,则返回right(因为结束循环后,left > right,结果需要向下取证,所以返回较小的right)
代码实现(java)
class Solution {
public int mySqrt(int x) {
if (x == 0 || x == 1) {
return x;
}
int left = 1;
int right = (x / 2) + 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (mid < x / mid) left = mid + 1;
else if (mid > x / mid) right = mid - 1;
else return mid;
}
return right;
}
}