一、二分查找
题目链接:704.二分查找
/**
* <pre>
* <p>最朴素的二分查找问题</p>
*
* 可以采用while循环进行查找,也可以采用递归进行查找
* 原理都一样,每次选择中间值进行判断,不断缩小查找的区间
* 左右指针不断靠拢,直到左右指针重叠时如果还找不到,这时要么左指针+1大于右指针,要么右指针-1小于左指针,这时便会结束循环或结束递归
* 查找区间分为左闭右闭和左闭右开,具体差别会在下面解释
* </pre>
*
* @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
* @date 2023/1/2 11:44
*/
public class 二分查找704 {
public int search1(int[] nums, int target) {
// 需要注意的是这里右边界是长度-1,因为数组下标从0开始
// 这里维护的是一个左闭右闭的区间
return bisection(nums, 0, nums.length-1, target);
}
// 递归方式
public int bisection(int[] nums, int left, int right, int target) {
// 左指针已经位于右指针右边,表示查询不到结果
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
return bisection(nums, mid+1, right, target);
} else {
return bisection(nums, left, mid-1, target);
}
}
// 循环方式——左闭右闭(更好理解)
public int search2(int[] nums, int target) {
int left = 0, right = nums.length - 1;
// 1.这里之所以使用的是<=号是因为维护的是左闭右闭的区间,所以两者相等时的值是有意义的
// 2.如果是左闭右开区间,则上面的right指针不需要-1,直接等于长度
while (left <= right) {
int mid = (right - left) / 2 + left;
int num = nums[mid];
if (num == target) {
return mid;
} else if (num > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
// 循环方式——左闭右开
public int search3(int[] nums, int target) {
int left = 0, right = nums.length;
// 这里的长度不需要-1,因为维护的是左闭右开区间
// 这时候当num<target的时候,right=mid,同理也不需要-1
// 当num>target的时候,left=mid+1,因为左闭且mid值已经判断过了,可以判断下一个,所以需要加1
// 循环结束条件:
// 因为右边是开区间,所以其实在这个位置的值是无效的,所以只需要left<right不需要等于
// 当相等时已经是取到无效值了就不用继续了
// 如何理解呢?
// 出现left=right有两种可能,要么是左边界=mid+1后等于右边界,要么是右边界=mid后等于左边界
// 如果是左边界=mid+1后等于右边界,因为右边界是开区间,所以其实已经超过范围了,显然就应该结束了
// 如果是右边界=mid后等于左边界,则mid是在前一轮就判断过的,故也不需要继续这新的一轮循环
while (left < right) {
int mid = (right - left) / 2 + left;
int num = nums[mid];
if (num == target) {
return mid;
} else if (num > target) {
right = mid;
} else {
left = mid + 1;
}
}
return -1;
}
}
二、搜索插入位置
题目链接:35.搜索插入位置(opens new window)
/**
* <pre>
* <p>此题其实就是二分查找后对结果进行一定的处理</p>
*
* 需要将mid值进行保存,循环结束后mid值其实就是最后一次查询时指针所在的位置(即进行判断的位置)
* 如果该位置正好是答案那么自然返回该位置就行了
* 如果该位置不是答案,那么只有两种情况
* 一种是最后一次判断的值比要找的数大,那么这个位置自然就是插入的位置
* 最后一次判断的值小的话,自然就要将目标值放在它后面,即+1再返回
* </pre>
*
* @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
* @date 2023/1/2 15:17
*/
public class 搜索插入位置35 {
public int searchInsert(int[] nums, int target) {
int left=0, right=nums.length-1, mid=0;
while (left <= right) {
mid = (right - left) / 2 + left;
if (nums[mid] == target) {
break;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (nums[mid] >= target) { // 如果mid位置所在的就是目的值则返回当前值,如果比目的值大则将目的值插在此处,原值后移
return mid;
} else {
return mid + 1; // 如果mid所在值比目的值小,则将目的值插在mid后面一位
}
}
}
三、在排序数组中查找元素的第一个和最后一个位置
题目链接: 34.在排序数组中查找元素的第一个和最后一个位置
/**
* <pre>
* <p>此题其实就是二分查询左右两个边界,找到目标值时不像一般的二分查找直接返回,而是继续查询直到找到最边缘的值</p>
*
* 可以采用两次二分,一次查询左边界,一次查询右边界
* 也可以采用一次二分,在找到目标值时向左右同时扩展
* </pre>
*
* @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
* @date 2023/1/2 12:11
*/
public class 在排序数组中查找元素的第一个和最后一个位置34 {
public int[] searchRange(int[] nums, int target) {
int[] result = {-1, -1};
bisection(nums, 0, nums.length-1, target, result);
return result;
}
public void bisection(int[] nums, int left, int right, int target, int[] result) {
if (left > right) {
return;
}
int mid = (right - left) / 2 + left;
if (nums[mid] == target) {
if (result[0] == -1) { // 第一次找到相同的值,则对两个位置全赋值
result[0] = mid;
result[1] = mid;
} else if (mid < result[0]) { // 不是第一次找到而且找到的位置比之前找到的左边界还要小,则扩展左边界
result[0] = mid;
} else if(mid > result[1]) { // 不是第一次找到而且找到的位置比之前的右边界还要大,则扩展右边界
result[1] = mid;
}
// 找到一个值后需要向左右两处继续查询是否还有其他相等情况以扩充边界
bisection(nums, mid+1, right, target, result);
bisection(nums, left, mid-1, target, result);
} else if (nums[mid] < target) {
bisection(nums, mid+1, right, target, result);
} else {
bisection(nums, left, mid-1, target, result);
}
}
// 使用两个二分分开查找左边界和右边界
public int[] searchRange2(int[] nums, int target) {
int[] res = new int[] {-1, -1};
res[0] = binarySearch(nums, target, true);
res[1] = binarySearch(nums, target, false);
return res;
}
public int binarySearch(int[] nums, int target, boolean leftOrRight) {
int res = -1;
int left = 0, right = nums.length - 1, mid;
while(left <= right) {
mid = left + (right - left) / 2;
if(target < nums[mid])
right = mid - 1;
else if(target > nums[mid])
left = mid + 1;
else {
res = mid;
// 当前查询的是左边界
if(leftOrRight)
right = mid - 1;
else
left = mid + 1;
}
}
return res;
}
}
四、x 的平方根
题目链接: 69.x 的平方根
/**
* <pre>
* <p>求x的平方根,实际也是二分查找的一个变形</p>
* 搜索区间从给定数组,编程1到x的区间
* 不断二分该区间,对每次取的mid值进行平方,与x值进行对比决定下一个区间变换
* </pre>
*
* @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
* @date 2023/1/2 15:47
*/
public class x的平方根69 {
public int mySqrt(int x) {
int left = 1, right = x, mid = 0; // mid=0解决不进入循环时(即x=0)的情况
long pow = 0; // 需要设置为long类型防止溢出
while (left <= right) {
mid = (right - left) / 2 + left;
pow = (long) mid * mid; // 需要先将一个mid转换为long类型,long*int才会为long类型,不然是溢出后的结果赋值给pow
if (pow == x) {
return mid;
} else if (pow < x) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (pow <= x) {
return mid;
} else {
return mid - 1;
}
}
}
五、有效的完全平方数
题目链接:367.有效的完全平方数
/**
* <pre>
* <p>求x平方根的简化版</p>
* </pre>
*
* @author <a href="https://github.com/kil1ua">Ken-Chy129</a>
* @date 2023/1/3 10:30
*/
public class 有效的完全平方数367 {
public boolean isPerfectSquare(int num) {
int left = 0, right = num;
long mid, pow;
while (left <= right) {
mid = (right - left) / 2 + left;
pow = mid * mid;
if (pow == num) {
return true;
} else if (pow < num) {
left = (int) (mid + 1);
} else {
right = (int) (mid - 1);
}
}
return false;
}
}