1. 数组
数组是一种具有连续储存空间的数据结构,具有以下特点:
- 起始索引为0
- 存储空间的连续性
- 添加、删除通过移动数组内元素实现,效率较低
- 查询效率较高
2. 二分查找
基本思想:对一升序的int数组,通过不断地修改left、right索引查询中间元素middle是否与目标元素target相同,若 t a r g e t < m i d d l e target < middle target<middle,则 r i g h t = m i d d l e − 1 right = middle - 1 right=middle−1;若 t a r g e t > m i d d l e target > middle target>middle,则 l e f t = m i d d l e + 1 left = middle + 1 left=middle+1;若 t a r g e t = m i d d l e target = middle target=middle,则算法返回该值索引。
public class Solution {
public int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1; // 搜索范围[left, right]
if (target < arr[0] || target > arr[right]) return -1;
// 搜索失败时[right + 1, right] 即left > right
while (left <= right) {
int middle = left + ((right - left) >> 1);
if (arr[middle] == target) {
return middle;
} else if (arr[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return -1;
}
}
2.1 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
链接:https://leetcode.cn/problems/search-insert-position
思路
给定数组已排好序,且时间复杂度为 O ( l o g n ) \text O(logn) O(logn),考虑使用二分查找。
二分查找搜索情况:
- 当 t a r g e t < a r r [ 0 ] target < arr[0] target<arr[0]时, l e f t = 0 left=0 left=0, r i g h t = − 1 right = -1 right=−1。
- 当 t a r g e t > a r r [ r i g h t ] target > arr[right] target>arr[right]时, l e f t = r i g h t + 1 left = right + 1 left=right+1, r i g h t = a r r . l e n g t h − 1 right = arr.length - 1 right=arr.length−1。
- 当 a r r [ 0 ] < = t a r g e t < = a r r [ r i g h t ] arr[0] <= target <= arr[right] arr[0]<=target<=arr[right]且 t a r g e t target target不存在数组时,有 a r r [ r i g h t ] < t a r g e t < a r r [ l e f t ] arr[right]< target < arr[left] arr[right]<target<arr[left]
- 当查询成功时,有 i n d e x i n { 1 , . . . , n } index\ in \ \{1,...,n\} index in {1,...,n}
综上所述:对于查找成功地元素则直接返回其索引。而对于查找失败地元素,则返回其 l e f t left left值作为插入位置。
public class Solution {
public int searchInsert(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int middle = left + ((right - left) >> 1);
if (arr[middle] == target) {
return middle;
} else if (arr[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return left;
}
}
2.2 在排序数组中查找元素的第一个和最后一个位置
思路来自:https://blog.csdn.net/weixin_43373833/article/details/113763879?spm=1001.2014.3001.5506
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
思路
需要注意,非递减数组,因此,数组中存在重复元素。且需要设计一个 O ( l o g n ) O(log n) O(logn)的方法,因此考虑二分查找。
题目意思是寻找某一元素的左边界和右边界。因此,对于常规的二分查找算法,需要作一定的修改。
当算法查找 t a r g e t target target成功时:
- 若寻找左边界,则需要把搜索范围缩小,即 r i g h t = m i d d l e − 1 right = middle - 1 right=middle−1。当到达左边界后,有 r i g h t right right在目标值的左边,而退出循环的条件为 l e f t = r i g h t + 1 left = right + 1 left=right+1,所以 a r r [ l e f t ] = t a r g e t arr[left] = target arr[left]=target
- 若寻找右边界,则 l e f t = m i d d l e + 1 left = middle + 1 left=middle+1,同理 a r r [ r i g h t ] = t a r g e t arr[right] = target arr[right]=target
每查找成功一次,则记录其索引值。
当算法查找失败时,考虑超出边界、和不超出边界两种情况:
- 查询左边界时
- 若 t a r g e t target target大于数组中的所有元素时,有 r i g h t = a r r . l e n g t h − 1 right = arr.length - 1 right=arr.length−1, l e f t = r i g h t + 1 left = right + 1 left=right+1,所以, l e f t > = a r r . l e n g t h left >= arr.length left>=arr.length
- 当 t a r g e t target target没有超出边界,但不存在时,有 a r r [ l e f t ] ! = t a r g e t arr[left] != target arr[left]!=target
- 查询右边界时
- 当 t a r g e t target target小于数组中的所有元素时,有 r i g h t = − 1 right = -1 right=−1, l e f t = 0 left = 0 left=0,所以, r i g h t < 0 right < 0 right<0
- 当 t a r g e t target target不超出边界,但不存在时,有 a r r [ r i g h t ] ! = t a r g e t arr[right] != target arr[right]!=target
// 查询左边界
public int searchLeftBoard(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int middle = left + ((right - left) >> 1);
if (arr[middle] == target) {
//收缩查询范围
right = middle - 1;
} else if (arr[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
// 先判断是否超出边界,在判断arr[left]与target的值
if (left >= arr.length || arr[left] != target) {
return -1;
}
return left;
}
// 查询右边界
public int searchRightBoard(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int middle = left + (right - left) >> 1);
if (arr[middle] == target) {
//收缩查询范围
left = middle + 1;
} else if (arr[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
// 先判断是否超出边界,在判断arr[right]与target的值
if (right < 0 || arr[right] != target) {
return -1;
}
return right;
}
3. 总结
参考的那篇二分查找的文章非常好,思想上非常nice!
总结的规律
前提条件:设target为查询的目标对象,arr为整数数组(不含重复元素)。(注意这里一定是要整数数组,浮点数需要特别小心)且不存在重复元素时,有:
- 当 target < a r r [ 0 ] \text{target} < arr[0] target<arr[0]时,有 left = 0 , right = − 1 \text{left} =0,\text{right} = -1 left=0,right=−1。
- 当 target > a r r [ arr.length - 1 ] \text{target} > arr[\text{arr.length - 1}] target>arr[arr.length - 1]时,有 right = arr.length - 1, left = arr.length \text{right = arr.length - 1, left = arr.length} right = arr.length - 1, left = arr.length
- 当 arr[0] < target < arr[arr.length - 1] and target not in arr为true \text{arr[0] < target < arr[arr.length - 1] and target not in arr为true} arr[0] < target < arr[arr.length - 1] and target not in arr为true,有 arr[right] < target < arr[left] \text{arr[right] < target < arr[left]} arr[right] < target < arr[left]
若数组(含重复元素)存在重复元素且非递减,需要某个元素的第一个位置或最后一个位置。思路时不断缩小搜索区域。