二分查找是一种就nm很离谱的算法。
二分查找必须要保证待查找的数组是从小到大有序的。二分查找也叫折半查找,简单的说就是将我们需要查找的target与数组的中间mid进行比较,如果等于就可以直接返回mid了。最初我们设置的mid就是数组长度的一半(low + high) / 2
,当target小于mid的时候移动high让它成为为mid - 1
;反之将low移动成为high + 1
。
二分查找的模板(基础实现):
public int binarySearch(int[] nums, int target){
//离谱1:注意high的取值
int low = 0, high = nums.length - 1, mid;
//离谱2:注意while的判断条件
while (low <= high) {
mid = (low + high) / 2;
//离谱3:注意下面三个判断条件
if (target = nums[mid]) {
//返回mid所在的下表
return mid;
} else if (target < nums[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
有了这个模板,在做题的时候套就行,然后根据题里面的特殊要求根据改造。但是要时刻注意离谱的地方,不注意就会出错。
Leetcode33. 搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
题目中说保证时间复杂度为O(log n),那只能从二分查找法入手了。审题后我们发现,它给的数组可以理解为是“二分有序的”,即我们可以将这个数组分半,每一半都是从小到大有序的。
我们具体要做的是先判断前后哪个部分是完全有序的,这样方便我们进行二分查找。
public int search(int[] nums, int target) {
if ((nums.length == 0) || (nums == null)) {
return -1;
}
int low = 0, high = nums.length - 1, mid;
while (low <= high) {
mid = (low + high) / 2;
if (target == nums[mid]) {
return mid;
}
if (nums[low] < nums[mid]){//前半部分完全有序
if (target < nums[mid] && nums[low] <= target) {
high = mid - 1;
} else {
low = mid + 1;
}
} else {//后半部分完全有序
if (target > nums[mid] && nums[high] >= target) {
low = mid + 1;
} else {
high = mid - 1;
}
}
}
return -1;
}
Leetcode34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。
找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1,-1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
遇到这种要求时间复杂度为O(log n)的题就直接考虑二分查找,这个题的要求是返回一个有两个元素的数组,第一个为起始的索引,第二个为结束索引,所以我们在找到的时候还要向左右扩展找到范围,我们可以用一个布尔类型的flag,如果它为true我们就至扫左侧。
public int find(int[] nums, int target, boolean flag) {
int low = 0. high = nums.length, mid;
while (low < high) {
mid = (low + high) / 2;
//给flag表示为true,如果mid就是
if ((target < nums[mid]) || (flag && target == nums[mid])) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
public int[] searchRange(int[] nums, int target) {
int[] arr = {-1,-1};
int result = find(nums, target, true);
if ((nums[result] != target) || (result == nums.length)) {
return arr;
}
arr[0] = result;
arr[1] = find(nums, target, false) - 1;
return arr;
}
Leetcode35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
没找到就返回被插入的位置,这个位置其实就是我们最终没有搜索到时low的下标,做题的时候就是没想到这一点,看来还是二分查找理解的不到位。
public int searchInsert(int[] nums, int target) {
int low = 0, high = nums.length - 1, mid;
while (low <= high) {
mid = (high + low) / 2;
if (nums[mid] == target) {
return mid;
} else if (target < nums[mid]){
high = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}