二分查找
二分查找是一种思想,双指针是实现的“工具”。二分法有以下几种情况
1. 有序无重复模板
用于有序,无重复数组
class Solution {
public int search(int[] nums, int target) {
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
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 if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
}
2. 有序重复模板
用于有序,有重复元素的数组,寻找target边界
class Solution {
public int search(int[] nums, int target) {
//方法1:从前到后遍历找到目标值。然后统计个数
//方法2:由于数组有序,可以采用二分法进行查找
if (nums.length == 0) return 0;
int leftBoard = getLeftBoard(nums, target);
int rightBoard = getRightBoard(nums, target);
if (leftBoard == -1 || rightBoard == -1) return 0;
return rightBoard - leftBoard + 1;
}
private int getLeftBoard(int[] nums, int target) {
int left = 0;
int right = nums.length;
int res = 0;
int mid = 0;
while (left < right) { //左闭右开区间
mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid;
res = mid; //左边界
} else if (nums[mid] < target) {
left = mid + 1;
}
}
if (nums[res] != target) return -1;
return res;
}
private int getRightBoard(int[] nums, int target) {
int left = 0;
int right = nums.length;
int res = 0;
int mid = 0;
while (left < right) { //左闭右开区间
mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] <= target) {
res = mid; //右边界
left = mid + 1;
}
}
if (nums[res] != target) return -1;
return res;
}
}
3. 基于模板的发散题
35.返回插入元素的位置
-
x的平方根
-
有效的完全平方数
4. 旋转排序数组(无重复元素)
class Solution {
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
/**
这个寻找最小值,和寻找目标值不同,这里的二分是要缩小到最小点的位置,所以每次都需要判断mid落到哪个区域,如果是左侧区域(大数区域)则令left = mid + 1(走出大数区域,所以要加1);如果是落在了右侧区域,则令right = mid (不能让他进入左侧区域,所以不能用mid - 1)
*/
while (left <= right) {
int mid = left + (right - left) / 2;
if (left == right) return nums[left];
if (nums[mid] < nums[right]) {
right = mid;
} else {
left = mid + 1;
}
}
return -1;
}
}
5. 旋转数组排序(有重复元素)
class Solution {
public int findMin(int[] nums) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right]) right = mid;
else if (nums[mid] > nums[right]) left = mid + 1;
else right--;
/**
这里right不可能对应唯一的最小值,假设对应唯一最小值,那么怎么还存在nums[mid] == nums[right]? 所以让right--,破除“死局”
*/
}
return nums[left];
}
}
旋转排序数组(搜索目标值)
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
if (nums[0] <= nums[mid]) { //要看在哪个部分,在那个部分的边界在进行二分
if (target >= nums[0] && target < nums[mid]) { //左闭右闭范围
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target > nums[mid] && target <= nums[nums.length - 1]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
}