前言
参考代码随想录
【二分查找】
就是用两个指针在一个有序数组上一起找,每次区间缩小一半,重点在区间的范围(两个指针)什么时候变更,变更成什么样子。
【套路】
- 定义left、right 左闭右闭还是左闭右开
- while left 和right 注意边界
- while中定义mid
- 根据nums[mid]判断区间变化或者找到结果
第一题
2023.10.15
704. 二分查找
这个题没什么好说的,暴力也很简单
暴力解法
class Solution {
// 暴力解法
public int search(int[] nums, int target) {
int n = nums.length;
// 有序数组,避免运算
if (target < nums[0] || target > nums[n - 1]) {
return -1;
}
for (int j = 0; j < n; j++) {
if (nums[j] == target) {
return j;
}
}
return -1;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
二分查找
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
// 有序数组,避免运算
if (target < nums[0] || target > nums[n-1]) {
return -1;
}
int left = 0;
int right = n-1; // 左闭右闭
while (left <= right) { // 闭区间
int mid = left + ((right - left) >> 1); // 取中值,用位运算替换避免left+right可能的整数越界,等价于 区间起始坐标 + 区间长度/2
if (target == nums[mid]) // 先判断这个,避免多余if,找到直接返回mid
return mid;
else if (target < nums[mid])
right = mid - 1;
else
left = mid + 1;
}
return -1; // while完了还没找到则不存在
}
}
时间复杂度:O(log n)
空间复杂度:O(1)
第二题
69. x 的平方根
这题不难,关键在于把0-x和区间对应起来
暴力解法
class Solution {
public int mySqrt(int x) {
// 暴力
if (x <= 1) return x;
int sqrt = 1; // 从1开始猜测平方根
while (true) {
int nextSqrt = sqrt + 1; // 尝试下一个数
// 如果下一个数的平方小于等于x并且大于当前的sqrt,那么接近了
if (nextSqrt <= x / nextSqrt) sqrt = nextSqrt;
else return sqrt;
}
}
}
时间复杂度:O(√n) 注:n>√n>log n
空间复杂度:O(1)
二分查找
class Solution {
public int mySqrt(int x) {
if (x <= 1) return x;
// 二分查找
int left = 0;
int right = x / 2;
while (left <= right) {
int mid = left + (right - left) / 2;
long square = (long) mid * mid; // 用long类型来避免整数溢出
if (square == x) {
return mid;
} else if (square < x) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 最后返回right,循环结束时非精确平方根的right更趋近整数部分
return right;
}
}
第三题
35. 搜索插入位置
这题也很简单,不懂的先看下面二分,考虑清楚有四种情况
暴力解法
class Solution {
public int searchInsert(int[] nums, int target) {
// 暴力解法
// 1. 目标值最小
if (target < nums[0]) return 0;
// 2. 目标值最大
if (target > nums[nums.length - 1]) return nums.length;
for (int i = 0; i < nums.length; i++) {
if (nums[i] >= target) {
return i;
}
}
return nums.length;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
二分查找
class Solution {
public int searchInsert(int[] nums, int target) {
// 二分查找 但这道题有四种情况
int left = 0;
int right = nums.length - 1;
// 1. 目标值最小
if (target < nums[0]) return 0;
// 2. 目标值最大
if (target > nums[right]) return right + 1;
// 3. 目标值在数组元素中
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target) return mid;
else if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
// 4. 目标值在数组元素之间
return right + 1;
}
}
时间复杂度:O(log n)
空间复杂度:O(1)
第四题
这道题也很简单,只是返回从单个的索引变成了区间数组。注意数组里只有1个目标数的时候,起始索引和终止索引相同
暴力解法
class Solution {
public int[] searchRange(int[] nums, int target) {
// 暴力解法,这道题暴力解法反而很简单
int[] array = new int[]{-1,-1};
// 边界情况
if (nums.length == 0 || target < nums[0] ||
target > nums[nums.length - 1]) return array;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
if (array[0] == -1){
array[0] = i;
array[1] = i; // nums元素一个的情况
} else array[1] = i;
}
}
return array;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
二分查找
class Solution {
int[] searchRange(int[] nums, int target) {
int[] array = new int[]{-1,-1};
// 边界情况
if (nums.length == 0 || target < nums[0] || target > nums[nums.length - 1]) return array;
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况三
if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
return array;
}
int getRightBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // target在右半区间的情况
left = middle + 1; // 不断更新left,left不断右移,最终会移动到nums[middle] == target的时候
rightBorder = left; // 这时候middle就是右边界
}
}
return rightBorder;
}
int getLeftBorder(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
}
……第四题的二分是代码随想录那边的答案
但是有点疑惑,主要在右边界赋左值的时候,不懂到底什么状态
改天再思考一下