当前位置:   article > 正文

代码随想录-01-二分查找-LeetCode704二分查找_代码随想录二分

代码随想录二分

前言

“代码随想录”刷题记录。总结笔记均会放在“算法刷题-代码随想录”该专栏下,以下为原文的链接。
二分查找卡尔题解

题目

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:

你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

1. 判断是否使用二分查找的3个前提条件

当题目中包含数据结构为“数组”(比如链表就不可以使用二分查找,因为二分查找只能内存地址连续的数据结构中进行),并且该“数组为有序数组”以及“数组中无重复元素”这三个前提条件,很有可能可以使用“二分查找”。

2. 二分查找算法思路:

思路

  • 1.如果待查列表不为空,将他的中间元素与目标元素进行比较,相等则返回下标并结束;
  • 2.如果不相等,则比较目标元素与中间元素的大小,
  • 3.若目标元素大于中间元素,则把原序列以中间元素开始的右半部分当做新的序列,在新序列中继续查找该目标元素,
  • 4.若目标元素小于中间元素,则把原序列以中间元素结尾的左半部分当做新的序列,在新序列中继续查找该目标元素,
  • 5.再在新序列中共重复第一步的操作。
    优点:时间复杂度为log(n),比普通查找快。
    缺点:如下
  • 要求数据结构在内存中连续存储(如:数组),并且有序(升序、降序均可)且不重复。
  • 数据结构插入删除困难。
    详情链接

3. 算法实现

  • 区间:通过两个int类型(left、right)变量充当左右指针的值,来规定区间范围,我选择的区间边界是左闭右闭(5.算法坑点中会讨论这个问题),一个int类型(mid)变量充当中间值的下标。
    区间的大小应该大于等于1,也就是说左指针应该不小于右指针,即left <= right。
    若区间范围小于1,还未找到,则说明该区间不含有目标值。
  • 通过循环实现判定目标值和中间元素值大小的比较。
  • 注意,找中间位置值时未避免int数值溢出,应该为 mid = left + ((right - left) >> 1);
    代码:
    Java版本
//左闭右闭  right = nums.length - 1, while的条件为  left <= right
class Solution {
public int search(int[] nums, int target) {        
     //左闭右闭的写法
     int left = 0,right = nums.length - 1,mid = 0;
     while(left <= right){
         mid = left + ((right - left) >> 1);//防止溢出
         if(target > nums[mid]){
             left = mid + 1;
         }else if(target < nums[mid]){
             right = mid - 1;
         }else{
             return mid;
         }
     }
     return -1;
 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

C++版本

#include<stdio.h>
#include<vector>
using namespace std;
int binarySearch(vector<int> nums,int target){
	int left = 0,right = nums.size() - 1,mid = 0;
	while(left <= right){
		mid = left + (right - left) / 2;//right肯定大于left,故right - left不会导致溢出Integer的最大值;并且整个表达式等同于 (right + left)/ 2,可以自己试着将两者转换
		if(target < nums[mid]){
			right = mid- 1;
		}else if(target > nums[mid]){
			left = mid + 1;
		}else{
			return mid;
		}
	}
	return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

4. 算法分析

n为数组长度
时间复杂度:O(logn)
空间复杂度:O(1),只增加了left、right、mid,这3个变量。(算法中的nums虽然空间大小是n,但是在计算空间复杂度时只计算额外增加的空间大小)

5. 算法的坑

  • 区间可以选择,左闭右闭或者左闭右开

    1. 左闭右闭:
      left = 0,right = nums.length - 1 ,注意这里是右闭的精髓,就是右边的边界是数组的最后一位,
      那么左右双指针满足构成区间的条件则是,left<=right,注意left==right 时也能构成区间,此时区间大小为1。
    1. 左闭右开:
      left=0,right = nums.length,注意这里是右开的精髓,就是右边的指针是数组最后一位的下一个位置,(即取不到数组的最后一个元素),
      那么左右双指针满足构成区间的条件则是,left < right,因为当left == right时,区间大小为0,因为右指针是区间最右元素的下一位,那么想大于此时右区间是在left的左边一位,则不满足区间大小至少为1的条件。
  • 中间值mid最好写成mid = l + (r - l) / 2,而不是容易想到的mid= (l + r) / 2;
    因为l + r的值可能超过int的最大范围而造成溢出。
    为什么二分法不建议使用 (right + left) / 2?相关链接

  • 什么叫做“循环不变量”
    在每次循环中都满足的条件,在写循环时要清楚循环中的循环不变量是什么。
    举例
    二分法中的循环不变量是,每次循环时查找target的额区间的区间边界是开还是闭

  • C++中右移一位(>> 1),在符号的运算中相当于除以2,且右移一位的速度快于/2的速度
    并且注意
    右移运算的优先级在所有操作符中是最低的,所以如果右移操作在最后,则需要打上括号
    C++位右移是什么
    轻松理解 left + ((right -left) >> 1)

>>是算术右移(需要考虑符号位,正数左边补0,负数左边补1),>>>是逻辑右移(不需要考虑符号位,左边统一补0)
链接是右移的例子,位移运算时数为它的补码形式。

6.做题记录

  • 3刷忘记了二分算法的实现思路(T~T),不过稍微看了下卡哥的做题思路就回想起来了,看来还是得多温习巩固。

同类题型

35. 搜索插入位置

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/561254
推荐阅读
相关标签
  

闽ICP备14008679号