博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
算法-二分查找
阅读量:2181 次
发布时间:2019-05-01

本文共 9078 字,大约阅读时间需要 30 分钟。

算法-二分查找

1 搜索插入位置

1.1 题目出处

https://leetcode-cn.com/problems/search-insert-position/

1.2 题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5

输出: 2
示例 2:

输入: [1,3,5,6], 2

输出: 1
示例 3:

输入: [1,3,5,6], 7

输出: 4
示例 4:

输入: [1,3,5,6], 0

输出: 0

1.3 二分查找-递归版本

1.3.1 思路

直接在区间[0, nums.length - 1] 二分查找,直到找到了相等的元素直接返回位置,或者没找到,此时start > end

start > end时上一轮肯定是start==end,此时分析如下:

  • 如果是target < nums[mid],则end=mid-1,也就是说target比mid数小,应该放在start处,而mid数右移
  • 如果是target > nums[mid],则start=mid + 1,也就是说target比mid数大,应该放在原start右边一位

综上,这里只需要直接返回start就是没找到该数时应该放入的位置。

1.3.2 代码

class Solution {
public int searchInsert(int[] nums, int target) {
if (nums == null){
return -1; } return find(nums, target, 0, nums.length - 1); } private int find(int[] nums, int target, int start, int end){
if(start > end){
// 这种情况,上一轮肯定是start==end // 如果是target < nums[mid],则end=mid-1,也就是说target比mid数小,应该放在start处,而mid数右移 // 如果是target > nums[mid],则start=mid + 1,也就是说target比mid数大,应该放在原start右边一位 // 综上,这里只需要直接返回start就是没找到该数时应该放入的位置 return start; } int mid = (start + end) / 2; if(target == nums[mid]){
// 如果找到了就返回该位置 return mid; }else if(target < nums[mid]){
return find(nums, target, start, mid - 1); }else{
return find(nums, target, mid + 1, end); } }}

1.3.3 时间复杂度

O(logN)

1.3.4 空间复杂度

O(logN)

  • 递归,循环版本可改进为O(1)

1.4 二分查找-循环版本

代码如下:

class Solution {
public int searchInsert(int[] nums, int target) {
if(nums == null || nums.length == 0){
return -1; } int start = 0; int end = nums.length - 1; int mid; while(start <= end){
mid = (start + end) /2; if(target == nums[mid]){
return mid; }else if(target < nums[mid]){
end = mid - 1; }else{
start = mid + 1; } } return start; }}

2 搜索左右边界

2.1 题目出处

https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/

2.2 题目描述

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8

输出: [3,4]
示例 2:

输入: nums = [5,7,7,8,8,10], target = 6

输出: [-1,-1]

2.3 二分查找-递归

2.3.1 思路

直接在区间[0, nums.length - 1] 二分查找,直到start > end或找到了相等元素:

  • start > end时,说明找不到,此时返回-1, -1
  • 找到了相等元素,就分别往左和右继续搜索该元素,直到不相等时结束

最坏情况,可能元素全部相等,此时时间复杂度为O(N)

2.3.2 代码

class Solution {
private int[] result = {
-1, -1}; public int[] searchRange(int[] nums, int target) {
if(nums == null || nums.length == 0){
return result; } return search(nums, target, 0, nums.length - 1); } private int[] search(int[] nums, int target, int start, int end){
if(start > end){
return result; } int mid = (start + end) / 2; if (nums[mid] == target){
result[0] = mid; result[1] = mid; for(int i = mid - 1; i >=0; i--){
if(nums[i] < target){
break; }else{
result[0] = i; } } for(int i = mid + 1; i < nums.length; i++){
if(nums[i] > target){
break; }else{
result[1] = i; } } return result; }else if (nums[mid] < target){
return search(nums, target, mid + 1, end); }else {
return search(nums, target, start, mid - 1); } }}

2.3.3 时间复杂度

最坏情况O(N)

2.3.4 空间复杂度

最坏情况O(N)

2.4 二分查找-循环+优化

2.4.1 思路

2.3方法最坏情况,可能元素全部相等,此时时间复杂度为O(N)。

题目要求时间复杂度O(logN),也就是说左右边界也必须通过二分查找实现。

2.4.2 代码

class Solution {
private int[] result = {
-1, -1}; public int[] searchRange(int[] nums, int target) {
if(nums == null || nums.length == 0){
return result; } int start = 0; int end = nums.length - 1; int mid = 0; int targetIndex = -1; while(start <= end){
mid = (start + end) /2; if(target == nums[mid]){
targetIndex = mid; break; }else if(target < nums[mid]){
end = mid - 1; }else{
start = mid + 1; } } int left = -1; int right = -1; if(targetIndex > -1){
// 此时说明找到了一个相等的 left = targetIndex; right = targetIndex; // 继续查找左边界 start = 0; end = mid - 1; while(start <= end){
int mid2 = (start + end) / 2; if(target == nums[mid2]){
left = mid2; end = mid2 - 1; }else if(target < nums[mid2]){
end = mid2 - 1; }else{
start = mid2 + 1; } } // 查找右边界 start = mid + 1; end = nums.length - 1; while(start <= end){
mid = (start + end) / 2; if(target == nums[mid]){
right = mid; start = mid + 1; }else if(target < nums[mid]){
end = mid - 1; }else{
start = mid + 1; } } result[0] = left; result[1] = right; } return result; }}

2.4.3 时间复杂度

O(logN)

2.4.4 空间复杂度

O(1)

3 搜索旋转排序数组

3.1 题目出处

https://leetcode-cn.com/problems/search-in-rotated-sorted-array/

3.2 题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0

输出: 4
示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3

输出: -1

3.3 二分查找

3.3.1 思路

二分搜索,如果mid就是目标就直接返回,否则:

  • target < nums[mid]

    还需要判断target是否大于等于nums[start]:

    • 如果是就说明就在 [start,mid-1]区间搜索即可,只需要调整end为mid-1
    • 否则就表明start->mid无序,此时只能把nums[start]排除掉,更新start++
  • target > nums[mid]

    还需要判断target是否小于等于nums[end]:

    • 如果是就说明就在 [mid,end]区间搜索即可,只需要调整start为mid+1
    • 否则就表明mid->end无序,此时只能把nums[end]排除掉,更新end–

这样虽然能找到目标元素,但问题在于最坏情况时间复杂度为O(N)

3.3.2 代码

class Solution {
public int search(int[] nums, int target) {
int result = -1; if(nums == null || nums.length == 0){
return result; } int start = 0; int end = nums.length - 1; int mid; while (start <= end) {
mid = (start + end) / 2; if (nums[mid] == target) {
result = mid; break; } if (target < nums[mid]){
// target 在mid左边 if(target >= nums[start]){
// 这表明start->mid有序,且target位于其间,只需要减小右边界 end = mid - 1; }else{
// 要么start->mid无序,此时只能把nums[start]排除掉 start++; } }else{
if(target <= nums[end]){
// 这表明mid->end有序,且target位于其间,只需要增长左边界 start = mid + 1; }else{
// 要么mid->end无序,此时只能把nums[end]排除掉 end--; } } } return result; }}

3.3.3 时间复杂度

最坏情况O(N)

3.3.4 空间复杂度

O(1)

3.4 二分查找-优化

3.4.1 思路

前面算法最坏情况时间复杂度为O(N),因为我们无法在某些情况判断到底应该将边界移动到哪儿,只能一个一个移动。

我们重新考虑,仍然二分搜索,如果mid就是目标就直接返回,否则:

  • nums[start] <= nums[mid]

    此时[start, mid]有序,还需要判断target是否处于[start, mid]中:

    • 如果是就继续在[start, mid - 1]中查找,更新end = mid - 1
    • 否则就继续在[mid+1, nums.length - 1]中查找,更新start = mid + 1
  • 否则nums[mid] < nums[start]

    此时[mid, end]有序,还需要判断target是否处于[mid, end]中:

    • 如果是就继续在[mid + 1, end]中查找,更新start = mid + 1
    • 否则就继续在[0, mid - 1]中查找,更新end = mid - 1

可以看到,我们现在首先关注的不再是target而是mid,每次都找到mid所在的有序区间,然后再判断target。

3.4.2 代码

class Solution {
public int search(int[] nums, int target) {
int result = -1; if(nums == null || nums.length == 0){
return result; } int start = 0; int end = nums.length - 1; int mid; while (start <= end) {
mid = (start + end) / 2; if (nums[mid] == target) {
result = mid; break; } if (nums[start] <= nums[mid]) {
if (nums[start] <= target && target < nums[mid]) {
end = mid - 1; } else {
start = mid + 1; } } else {
if(nums[mid] < target && target <= nums[end]) {
start = mid + 1; }else{
end = mid - 1; } } } return result; }}

3.4.3 时间复杂度

O(logN)

3.4.4 空间复杂度

O(1)

转载地址:http://qlpkb.baihongyu.com/

你可能感兴趣的文章
了解 Sklearn 的数据集
查看>>
用ARIMA模型做需求预测
查看>>
推荐系统
查看>>
TensorFlow-11-策略网络
查看>>
浅谈 GBDT
查看>>
如何选择优化器 optimizer
查看>>
一文了解强化学习
查看>>
CART 分类与回归树
查看>>
seq2seq 的 keras 实现
查看>>
seq2seq 入门
查看>>
什么是 Dropout
查看>>
用 LSTM 做时间序列预测的一个小例子
查看>>
用 LSTM 来做一个分类小问题
查看>>
详解 LSTM
查看>>
按时间轴简述九大卷积神经网络
查看>>
详解循环神经网络(Recurrent Neural Network)
查看>>
为什么要用交叉验证
查看>>
用学习曲线 learning curve 来判别过拟合问题
查看>>
用验证曲线 validation curve 选择超参数
查看>>
用 Grid Search 对 SVM 进行调参
查看>>