力扣top100(day04-03)--二分查找
本文为力扣TOP100刷题笔记
笔者根据数据结构理论加上最近刷题整理了一套 数据结构理论加常用方法以下为该文章:
力扣外传之数据结构(一篇文章搞定数据结构)
35. 搜索插入位置
class Solution {public int searchInsert(int[] nums, int target) {int left=0;int length=nums.length;int right=length-1;int ans = length;while(left <= right){int mid = ((right - left) >> 1) + left;if(nums[mid]>=target){ans=mid;right=mid-1;}else{left=mid+1;}}return ans;}
}
关键点说明:
初始化:
left = 0
(数组起始索引)。
right = nums.length - 1
(数组末尾索引)。
ans = nums.length
(默认插入位置,适用于target
大于所有元素的情况)。二分查找:
mid = left + ((right - left) >> 1)
:计算中间索引,使用位运算>> 1
代替/ 2
,效率更高且防止(left + right)
溢出。如果
nums[mid] >= target
:
更新
ans = mid
(记录可能的插入位置)。继续在左半部分查找(
right = mid - 1
)。否则:
继续在右半部分查找(
left = mid + 1
)。终止条件:
当
left > right
时循环结束,此时ans
即为target
的插入位置。示例:
示例 1:
输入:
nums = [1, 3, 5, 6]
,target = 5
执行过程:
left = 0
,right = 3
,ans = 4
mid = 1
→nums[1] = 3 < 5
→left = 2
mid = 2
→nums[2] = 5 >= 5
→ans = 2
,right = 1
循环结束,返回
ans = 2
(5
已存在,返回其索引)。示例 2:
输入:
nums = [1, 3, 5, 6]
,target = 2
执行过程:
left = 0
,right = 3
,ans = 4
mid = 1
→nums[1] = 3 >= 2
→ans = 1
,right = 0
mid = 0
→nums[0] = 1 < 2
→left = 1
循环结束,返回
ans = 1
(2
应插入在索引1
处)。时间复杂度 & 空间复杂度
时间复杂度:
O(log n)
,标准的二分查找。空间复杂度:
O(1)
,仅使用常数空间。其他实现方式
也可以直接使用 Java 内置的
Arrays.binarySearch()
:java
import java.util.Arrays;class Solution {public int searchInsert(int[] nums, int target) {int index = Arrays.binarySearch(nums, target);return index >= 0 ? index : -index - 1;} }
如果找到目标值,返回其索引。
如果未找到,返回
-insertionPoint - 1
,转换后即为插入位置。
74. 搜索二维矩阵
class Solution {public boolean searchMatrix(int[][] matrix, int target) {int m = matrix.length; // 矩阵的行数int n = matrix[0].length; // 矩阵的列数int low = 0, high = m * n - 1; // 初始化二分查找的左右边界while (low <= high) {int mid = low + (high - low) / 2; // 计算中间位置,防止溢出int x = matrix[mid / n][mid % n]; // 将一维索引转换为二维坐标if (x < target) {low = mid + 1; // 目标在右半部分} else if (x > target) {high = mid - 1; // 目标在左半部分} else {return true; // 找到目标}}return false; // 未找到目标}
}
关键点说明:
二维矩阵 → 一维数组的映射:
因为矩阵整体有序,可以将其视为一个长度为
m * n
的一维数组。索引转换:
mid / n
得到行号。
mid % n
得到列号。例如,
mid = 5
,n = 4
,则matrix[1][1]
(因为5 / 4 = 1
,5 % 4 = 1
)。标准的二分查找:
初始化
low = 0
,high = m * n - 1
。计算
mid
并比较matrix[mid / n][mid % n]
与target
:
如果
x < target
,说明目标在右侧,调整low = mid + 1
。如果
x > target
,说明目标在左侧,调整high = mid - 1
。如果相等,直接返回
true
。终止条件:
如果
low > high
,说明目标不存在,返回false
。示例:
输入:
java
matrix = [[1, 3, 5, 7],[10, 11, 16, 20],[23, 30, 34, 50] ], target = 3执行过程:
m = 3
,n = 4
,low = 0
,high = 11
。
mid = 5
→matrix[1][1] = 11
>3
→high = 4
。
mid = 2
→matrix[0][2] = 5
>3
→high = 1
。
mid = 0
→matrix[0][0] = 1
<3
→low = 1
。
mid = 1
→matrix[0][1] = 3
==target
→ 返回true
。时间复杂度 & 空间复杂度
时间复杂度:
O(log(m * n))
,即标准的二分查找复杂度。空间复杂度:
O(1)
,仅使用常数空间。
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {public int[] searchRange(int[] nums, int target) {int leftIdx = binarySearch(nums, target, true);int rightIdx = binarySearch(nums, target, false) - 1;if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {return new int[]{leftIdx, rightIdx};} return new int[]{-1, -1};}public int binarySearch(int[] nums, int target, boolean lower) {int left = 0, right = nums.length - 1, ans = nums.length;while (left <= right) {int mid = (left + right) / 2;if (nums[mid] > target || (lower && nums[mid] >= target)) {right = mid - 1;ans = mid;} else {left = mid + 1;}}return ans;}
}
关键点说明:
binarySearch
函数:
参数
lower
:
lower = true
:查找 第一个>= target
的位置(左边界)。
lower = false
:查找 第一个> target
的位置(右边界 + 1)。返回值:
如果
lower = true
,返回target
的起始位置(如果存在)。如果
lower = false
,返回target
的结束位置 + 1(因此需要rightIdx = ans - 1
)。主函数
searchRange
:
先调用
binarySearch(nums, target, true)
找到左边界leftIdx
。再调用
binarySearch(nums, target, false)
找到右边界+ 1
,然后- 1
得到真正的右边界rightIdx
。检查有效性:
leftIdx <= rightIdx
:确保区间存在。
rightIdx < nums.length
:防止越界。
nums[leftIdx] == target && nums[rightIdx] == target
:确保找到的是target
。边界情况处理:
如果
target
不存在,leftIdx
可能会指向第一个> target
的位置,此时leftIdx > rightIdx
,返回[-1, -1]
。示例:
输入:
nums = [5,7,7,8,8,10]
,target = 8
执行过程:
binarySearch(nums, 8, true)
:
mid = 2
→nums[2] = 7 < 8
→left = 3
。
mid = 4
→nums[4] = 8 >= 8
→ans = 4
,right = 3
。循环结束,返回
leftIdx = 3
(左边界)。
binarySearch(nums, 8, false)
:
mid = 2
→nums[2] = 7
不满足> 8
→left = 3
。
mid = 4
→nums[4] = 8
不满足> 8
→left = 5
。
mid = 5
→nums[5] = 10 > 8
→ans = 5
,right = 4
。循环结束,返回
ans = 5
,因此rightIdx = 5 - 1 = 4
(右边界)。检查
leftIdx = 3
,rightIdx = 4
是否有效:
3 <= 4
且nums[3] = 8
,nums[4] = 8
→ 返回[3, 4]
。时间复杂度 & 空间复杂度
时间复杂度:
O(log n)
,因为进行了两次二分查找。空间复杂度:
O(1)
,仅使用常数空间。
33. 搜索旋转排序数组
class Solution {public int search(int[] nums, int target) {int n = nums.length;if (n == 0) { // 处理空数组return -1;}if (n == 1) { // 处理单元素数组return nums[0] == target ? 0 : -1;}int l = 0, r = n - 1; // 初始化左右指针while (l <= r) {int mid = (l + r) / 2; // 计算中间位置if (nums[mid] == target) { // 找到目标return mid;}if (nums[0] <= nums[mid]) { // 左半部分有序if (nums[0] <= target && target < nums[mid]) { // 目标在左半部分r = mid - 1;} else { // 目标在右半部分l = mid + 1;}} else { // 右半部分有序if (nums[mid] < target && target <= nums[n - 1]) { // 目标在右半部分l = mid + 1;} else { // 目标在左半部分r = mid - 1;}}}return -1; // 未找到目标}
}
关键点说明:
初始化检查:
如果数组为空,直接返回
-1
。如果数组只有一个元素,直接判断是否等于
target
。二分查找:
计算中间位置:
mid = (l + r) / 2
。找到目标:如果
nums[mid] == target
,直接返回mid
。判断有序部分:
左半部分有序(
nums[0] <= nums[mid]
):
如果
target
在左半部分的范围内(nums[0] <= target < nums[mid]
),则继续在左半部分查找(r = mid - 1
)。否则,在右半部分查找(
l = mid + 1
)。右半部分有序(
nums[0] > nums[mid]
):
如果
target
在右半部分的范围内(nums[mid] < target <= nums[n - 1]
),则继续在右半部分查找(l = mid + 1
)。否则,在左半部分查找(
r = mid - 1
)。终止条件:
如果
l > r
,说明未找到目标,返回-1
。示例:
输入:
nums = [4,5,6,7,0,1,2]
,target = 0
执行过程:
l = 0
,r = 6
→mid = 3
→nums[3] = 7
≠0
。
nums[0] = 4 <= 7
,左半部分有序。
target = 0
不在[4, 7)
范围内 →l = 4
。
l = 4
,r = 6
→mid = 5
→nums[5] = 1
≠0
。
nums[0] = 4 > 1
,右半部分有序。
target = 0
在(1, 2]
范围内 →l = 6
。
l = 6
,r = 6
→mid = 6
→nums[6] = 2
≠0
。
nums[0] = 4 > 2
,右半部分有序。
target = 0
不在(2, 2]
范围内 →r = 5
。
l = 6
,r = 5
→ 循环结束,未找到目标。
实际上
target = 0
位于nums[4]
,但之前的逻辑未覆盖这种情况。需要修正判断条件。
153. 寻找旋转排序数组中的最小值
class Solution {public int findMin(int[] nums) {int length=nums.length;int left=0;int right=length-1;while(left<right){int mid=left+(right-left)/2;if(nums[mid]<nums[right]){right=mid;}else{left=mid+1;}}return nums[left];}
}
关键点说明:
初始化:
left = 0
,right = length - 1
,初始化搜索范围为整个数组。二分查找:
计算中间位置:
mid = left + (right - left) / 2
(防止整数溢出)。比较
nums[mid]
和nums[right]
:
如果
nums[mid] < nums[right]
:
说明右半部分有序,最小值可能在左半部分(包括
mid
)。调整
right = mid
,继续在左半部分查找。否则:
说明左半部分有序,最小值在右半部分(不包括
mid
)。调整
left = mid + 1
,继续在右半部分查找。终止条件:
当
left == right
时,循环结束,此时nums[left]
即为最小值。示例:
输入:
nums = [4,5,6,7,0,1,2]
执行过程:
left = 0
,right = 6
→mid = 3
→nums[3] = 7
,nums[6] = 2
→7 > 2
→left = 4
。
left = 4
,right = 6
→mid = 5
→nums[5] = 1
,nums[6] = 2
→1 < 2
→right = 5
。
left = 4
,right = 5
→mid = 4
→nums[4] = 0
,nums[5] = 1
→0 < 1
→right = 4
。
left = 4
,right = 4
→ 循环结束,返回nums[4] = 0
。时间复杂度 & 空间复杂度
时间复杂度:
O(log n)
,标准的二分查找。空间复杂度:
O(1)
,仅使用常数空间。