二分查找
注:①二分查找个人感觉分为查找边界和具体值两种情况,相比来说具体值就是将nums[mid] == target
作为截止条件跳出,查找边界就按头尾指针相遇为截止。
②普通二分使用mid = (head + tail) / 2;
有可能产生溢出,可以用mid = head + ((tail - head) / 2);
防止溢出情况(除二操作还可用>>1右移位运算优化)
③二分查找内while循环判断条件通常有<和<=,这点可从最后跳出条件决定(取值区间是否存在,例如:用<时,跳出条件为头尾相遇,即取值区间内只存在一个值;而<=跳出是区间内无值)
在排序数组中查找元素的第一个和最后一个位置
题目实际就是在有序数组中,查找目标元素起始位置和终点位置。
查看目标元素的始末,可以看做查找目标元素的起始和目标元素+1的起始之差。
public int[] searchRange(int[] nums, int target) {
int start = findMid(nums, target);
int end = findMid(nums, target + 1) - 1;
if (start <= end) {
return new int[] {start, end};
}
return new int[] {-1, -1};
}
private int findMid(int[] nums, int target) {
int head = 0;
int tail = nums.length - 1;
int mid;
while (head <= tail) {
mid = head + ((tail - head) / 2);
if (nums[mid] < target) {
head = mid + 1;
} else {
tail = mid - 1;
}
}
return head;
}
普通二分查找具体数不一定能定位到相同数中的起始位置,所以不需要将nums[mid] == target
作为截止条件,查找边界即可。
在结果中,只要有目标数存在,start必然小于tail,
搜索旋转排序数组
给定一个排序但按某位旋转后的数组(如[4,5,6,7,0,1,2]),查找目标值。
①
public int search(int[] nums, int target) {
int head = 0;
int tail = nums.length - 1;
int mid;
while (head <= tail) {
mid = (head + tail) / 2;
if (nums[mid] == target) {
return mid;
}
//在有序段落内进行二分判断
//故先查找有序部分相对中值位置
if (nums[mid] >= nums[head]) {
//中值比头值大(中值必在头之后),说明中值以左为有序段落
//当目标值比头值大,比中值小,说明目标值在头和中之间
if (target >= nums[head] && target < nums[mid]) {
tail = mid - 1;
} else {
//当目标值不符合上述条件,就说明在中值之后
head = mid + 1;
}
} else {
//中值比头值小,说明中值以右是有序段落
//当目标值比中值大,比尾值小,说明目标值在中和尾之间
if (target > nums[mid] && target <= nums[tail]) {
head = mid + 1;
} else {
tail = mid - 1;
}
}
}
return -1;
}
采取查找具体值的写法,但在更改基准头尾时,需要额外判断(见注释)。
主要思维是中值划分出来的两部分一部分必然有序,另一部分部分有序,而二分查找思路只能在有序部分实行,所以需要判断有序部分的位置,若有序在中值左侧,则依据目标值在头-中之间进行判断;右侧则依据目标值在中-尾之间判断。
②
public int search(int[] nums, int target) {
int head = 0;
int tail = nums.length - 1;
int mid;
while (head <= tail) {
mid = (head + tail) / 2;
if (nums[mid] == target) {
return mid;
}
//当目标值比0值大,说明在旋转点以左
if (target >= nums[0]) {
if (nums[mid] < nums[0]) {
//当中值比0值小,说明中值位于旋转点以右,故中值右侧不用再判断
nums[mid] = Integer.MAX_VALUE;
}
} else {
//当目标值比0值小,说明在旋转点以右
if (nums[mid] >= nums[0]) {
//当中值比0值大,说明中值位于旋转点以左,故中值左侧不用再判断
nums[mid] = Integer.MIN_VALUE;
}
}
if (nums[mid] < target) {
head = mid + 1;
} else {
tail = mid - 1;
}
}
return -1;
}
依旧采用查找具体值情况写二分查找,该思路目的将目标值依据旋转点进行判断,并且依据结果改变mid值,例如位于目标值位于旋转点以右,而中值位于旋转点以左,那么将当前中值改为无限小,强制向右方查找即可。
搜索二维矩阵
二维有序矩阵查找对应值
public boolean searchMatrix(int[][] matrix, int target) {
int row = matrix.length;
int col = matrix[0].length;
int head = 0;
int tail = row * col - 1;
int mid;
while (head <= tail) {
mid = (head + tail) / 2;
if (matrix[mid / col][mid % col] == target) {
return true;
}
if (matrix[mid / col][mid % col] < target) {
head = mid + 1;
} else {
tail = mid - 1;
}
}
return false;
}
想象将二维展开成一维处理即可,注意行数用除,列数用取余即可。
寻找旋转排序数组中的最小值
public int findMin(int[] nums) {
int head = 0;
int tail = nums.length - 1;
int mid;
while (head < tail) {
mid = (head + tail) / 2;
if (nums[mid] < nums[tail]) {
tail = mid;
} else {
head = mid + 1;
}
}
return nums[head];
}
不同旋转数组实际都是由两个开头数不同的递增数组组成,那么头、中、尾三值可能有如下情况:
①头值<中值,中值<尾值:说明处于一个递增数组中
说明最小值在左侧,应调节右边界
②头值<中值,中值>尾值:说明中值处于两个数组交界
说明最小值在右侧(因为旋转后的数组右侧为小值,左侧为大值),应调节左边界
③头值>中值,中值>尾值:该情况不发生
④头值>中值,中值<尾值:说明中值处于左侧部分
说明最小值在左侧,应调整右边界
综上:
中值与尾值进行比较即可得出哪个边界需要调整,中值<尾值:调整右边界 中值>尾值:调整左边界
寻找峰值
查找数组中比其两侧值都大的数。
public int findPeakElement(int[] nums) {
int head = 0;
int tail = nums.length - 1;
int mid;
while (head < tail) {
mid = (head + tail + 1) / 2;
if (nums[mid] < nums[mid - 1]) {
tail = mid - 1;
} else {
head = mid;
}
}
return head;
}
该问题思路依据中值分析峰值所在位置。
若中值比左侧值小,则该中值及其右侧不可能成为峰值,且其左侧值可能为峰值,故需要调节右边界。
否则(中值>=左侧值)包含中值在内的右侧数可能称为峰值,故调节左边界。