一 LC35.搜索插入位置
题目要求:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为
O(log n)
的算法。
思路分析:
这是一个经典的二分查找位置,在之前力扣算法带刷——二分查找法_力扣mid-CSDN博客这篇博客中也有详细讲解过,我们需要在一个排序数组中找到目标值的位置,或者找出目标值应该插入的位置。由于数组是排序的,所以可以通过二分查找将时间复杂度优化为 O(log n)。
我们定义左、右两个指针分别指向数组头、尾元素,当左指针小于等于右指针时循环计算中间值mid,并比较mid指向的元素与目标值大小,如果相等直接返回mid即可,不等则判断谁更大,如果mid指向的元素更大,则缩短区间为[左指针,中间指针-1],否则缩短区间为[中间指针+1,右指针],直到找到与目标值相等的元素索引或者左指针大于右指针时,此时说明没有找到目标值,我们直接返回左指针,也就是该值应该插入的位置(此时它左侧的元素都小于它,它右侧的数据都大于它)。
完整代码示例:
public class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
}
二 LC74.搜索二维矩阵
题目要求:
给你一个满足下述两条属性的
m x n
整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数
target
,如果target
在矩阵中,返回true
;否则,返回false
。
思路分析:
由于每行的第一个元素大于前一行的最后一个元素,且每行元素非严格递增,因此整个矩阵可以认为是一个从左上到右下的升序数组。我们可以对这个虚拟的一维数组应用二分查找。
我们可以将矩阵看作是一个由 m * n
个元素组成的一维数组,其中第 mid
个元素对应的二维索引为:row = mid / n
, col = mid % n
,其中 n
是矩阵的列数。之后我们通过二分查找的方式查找 target,
初始化 left = 0
和 right = m * n - 1,
计算 mid
,并通过上面的公式找到 mid
对应的矩阵元素 matrix[row][col]
。
之后就是常规的二分查找操作了。如果 matrix[row][col] == target
,返回 true;
如果 matrix[row][col] < target
,将 left
移动到 mid + 1
;如果 matrix[row][col] > target
,将 right
移动到 mid - 1
。如果遍历完后未找到目标值,返回 false
。
完整代码示例:
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int m = matrix.length;
int n = matrix[0].length;
int left = 0;
int right = m * n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
int midVal = matrix[mid / n][mid % n]; // 映射到二维矩阵的行和列
if (midVal == target) {
return true;
} else if (midVal < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
}
三 LC34.在排序数组中查找元素的第一个和最后一个位置
题目要求:
吖吖
思路分析:
使用两次二分查找,分别查找左边界和右边界,每个边界的查找方法都是基础的二分查找,区别在于在找到对应索引后,要继续向两侧查找是否有其他目标值存在,如果存在则覆盖
完整代码示例:
class Solution {
public int[] searchRange(int[] nums, int target) {
int leftRange = -1; // 左边界
int rightRange = -1; // 右边界
leftRange = searchLeft(nums, target, leftRange); // 查找左边界
rightRange = searchRight(nums, target, rightRange); // 查找右边界
return new int[]{leftRange, rightRange}; // 返回左右边界数组
}
// 查找右边界
private int searchRight(int[] nums, int target, int rightRange) {
int left = 0; // 左边界(左闭右闭)
int right = nums.length - 1; // 右边界
int mid = 0; // 中间位置
while (left <= right) {
mid =(left+right)/2; // 计算中间位置,避免溢出
if (nums[mid] == target) {
rightRange = mid; // 更新右边界为当前位置
left = mid + 1; // 继续向右搜索
} else if (nums[mid] > target) {
right = mid - 1; // 目标值在左半部分,更新右边界
} else {
left = mid + 1; // 目标值在右半部分,更新左边界
}
}
return rightRange; // 返回右边界
}
// 查找左边界
private int searchLeft(int[] nums, int target, int leftRange) {
int left = 0; // 左边界
int right = nums.length - 1; // 右边界
int mid = 0; // 中间位置
while (left <= right) {
mid = (left+right)/2; // 计算中间位置,避免溢出
if (nums[mid] == target) {
leftRange = mid; // 更新左边界为当前位置
right = mid - 1; // 继续向左搜索
} else if (nums[mid] > target) {
right = mid - 1; // 目标值在左半部分,更新右边界
} else {
left = mid + 1; // 目标值在右半部分,更新左边界
}
}
return leftRange; // 返回左边界
}
}