1. 二分法

基本问题:根据给出的target,找有序数列中和target相关的某个位置,可以是找target在数组中的index,target开始出现、结束出现的位置
基本思想:先确定待查数据的范围(排除了一些不可能的范围),将大区间划分为了可能出现和不可能出现两个小区间,直到区间位空或区间足够小

1. 整数二分

有序连续数列查找位置,不一定是数组表示的

1. 最基本的二分法

【问题描述】:给出有序整型数组nums和整型target,寻找target在数组中出现的位置,若找不到则返回-1

public int binarySearch(int[] nums,int target){int left = 0;int right = nums.length-1;             //【1】while(left <= right){                  //【2】int mid = left + (right-left) / 2;if(target == nums[mid]){return mid;                    //【3】}else if(target < nums[mid]){right = mid - 1;               //【4.1】}else{left = mid + 1;                   //【4.2】}}return -1;                             //【5】}

代码中可变得地方有四个
【1】初始leftright的赋值、【2】while的终止条件、【3】找到nums[mid] = target时的赋值、【4】循环中取左右区间时边界赋值、【5】返回值

2. 初始和循环中涉及到的区间相关问题【1】【2】【4】

每次探测的区间可以是[left , right]的闭区间或是[left , right)的左闭右开区间及其他
【1】left、right的写法:应该保证数组不会越界(以right为例)。
【2】while的写法:应该保证区间为空时终止循环。
【4】循环中边界更新: 应该保证mid不出现再下一次循环中也就是不在下一个半区间里。

2.1 闭区间
  • 【1】:right = nums.length, 则index有可能取到nums.length 然而不存在nums[nums.length]这一项,所以是right = nums.length - 1;
  • 【2】:while(left <= right),因为left > right[left , right]区间为空,结束while循环
  • 【4】:left = mid + 1;right = mid - 1;
2.2 左闭右开
  • 【1】:可以写right = nums.length,因为右边界是开的所以不会取到nums[nums.length]这一项,不会有越界问题
  • 【2】:while(left > right),因为left = right时,[left , right)]区间为空,结束while循环
  • 【4】:left = mid + 1;right = mid;,下一轮right = mid时,取左区间实际上是[left , oldmid)不包含上一轮的mid

3. 返回值的问题【3】【5】

1. 找到target时返回其index,否则返回-1

【3】return mid;
【5】return -1;

2. 找到target时返回其index,否则找target可以插入的位置
  • 若数列无重复元素,找到target直接返回index。未找到target时就有两种选择:返回小于target的最大值的index,则target可以插入到index的下一个;返回大于target的最小值的index,则target可以插入到index的上一个。对应于之后所说的边界左侧或右侧。
  • 若数列有重复元素,则找到target时返回index就可以有很多选择,比如返回小于target的最大值的index,则target可以插入到index的下一个,返回第一次出现target的index,则target插入index的上一个;返回大于target的最小值的index,则target可以插入到index的上一个,返回最后一次出现target的index,则target可以插入index的下一个。未找到target时和无重复元素数列一样有两个返回选择即边界左侧或右侧。
1. 序列中有重复元素

按target值大小可以对序列有两种划分,此处target=4

  • 1 2 3 | 4 4 4 4 5 6 7
    可以用来找小于target的最大值即分界线左边或第一次出现target的位置即分界线右侧
  • 1 2 3 4 4 4 4 | 5 6 7
    可以用来找大于target的最小值即分界线右边或最后一次出现target的位置即分界线左侧
    即有两种划分方式每种划分方式,每种方式都可以返回边界线左侧或右侧的位置,从而得到和target相关的各个位置

划分方式和边界线在代码中的体现:

  • 划分方式:由【3】nums[mid] = target时更新左边界还是更新右边界决定。若更新右边界则相当于取了左半边,分界线逐渐左移最终得到靠左划分的方式也就是第一种划分;若更新左边界则相当于取了右半边,分界线逐渐右移得到靠右划分的也就是第二种划分。
  • 取边界线的左侧or右侧:看【2】while终止时left和right值决定【5】返回值.
    • 若是闭区间,while终止时left > right也就是left= right+1;left在right右边一个位置,分界线在left和right之间。所以取分界线左侧的则返回right或者left-1.取分界线右侧的则返回left或者right+1;为了记忆方便可以只选返回left某个形式。
    • 若是开区间while终止时left = right(见2.2).分界线没办法在leftright之间
      • 如果是第一种划分也就是nums[mid] = target时更新右边界的不断向左收缩的情况,left = right在分界线右边。取分界线左侧的则返回left - 1,取分界线右侧的则返回left
      • 如果是第二种划分也就是nums[mid] = target时更新左边界不断向右收缩的情况,此时left = right在分界线右边。取分界线左侧时返回left-1,取分界线右侧时返回left

主要是看while终止时left和right的值的关系,while终止时分界线在哪里
闭区间:left = right + 1,分界线在两者中间
开区间:left = right,向左收缩分界线更左,向右收缩分界线更右
可以写一个方法同时具有搜索左边界和右边界的功能,即设置一个boolean lower参数,true时搜索左边界,false时搜索右边界
搜索左边界即不断向左收缩:不仅nums[mid] > target需要更新右边界,nums[mid] = target时也要更新右边界,所以lower = true时可以把这两种情况合并,同时可以和搜索右边界时nums[mid] > target的情况继续合并。其他情况都会更新右边界
lico_34 在排序数组中查找元素的第一个和最后一个位置

//此处是闭区间,返回向左收缩或向右收缩的边界线的右侧,所以向右收缩找最后一个位置时注意最终的结果应该-1
public int binarySearch(int[] nums, int target, boolean lower) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = (left + right) / 2;if (nums[mid] > target || (lower && nums[mid] >= target)) {right = mid - 1;} else {left = mid + 1;}}return left;                          }
2. 序列中没有重复元素

按target的值划分序列可以将序列划分为两部分,分界线左边严格<target,分界线右边严格>target,此处target=4

  • 1 2 3 | 5 6 7 8.即只有唯一的划分方式,则向左收缩和向右收缩都正确,在代码中对应【3】nums[mid] = target时更新左边界或更新右边界都对。
  • 取边界线左侧or边界线右侧,和有重复元素时的讨论是一样的,代码改动相同。

4. 一些注意

  • 按照上面讨论对于分界线在数组首位的情况,返回的index可能是0也可能返回的是-1,可能是nums.length - 1也可能是nums.length,如果后面还用这个结果就需要根据自己写的代码特别注意
  • 在计算mid的值时,如果使用位移运算代替除法,要注意运算顺序的为题
    • /2是向零取整(有截断所以负数截断后变大了,右移一位是向下取整 所以负数且是奇数时右移和/2不等价)
    • 加法和右移运算同级,但是由于左结合 所以要给右移运算加括号
      int mid = left + ((right - left) >> 1);

2. 小数二分

  • 【2】 while终止条件:区间长度低于精度时结束
  • 【4】更换区间:取左区间:right = mid;取右区间:left=mid;
  • 【5】返回值 left和right都可以
final double eps = 1e-8;//保留6位小数
double binarySearch(double left, double right){while(right - left > eps){double mid = (left + hight) / 2;if(f(mid) > target){right = mid;}else{left = mid;}}return left;
}

3. 一些二分的练习题

见leetcode官网的二分查找题单hhh……

1. 使用二分思想的

1. lico_4 寻找两个正序数组的中位数

lico_4 寻找两个正序数组的中位数
利用的不断排除不可能区域,继续在可能区域中寻找的思想
相当于找两个数组{A,B}中第k(中位数在排序中的位置,根据两数组的总数分奇偶情况可以计算出来k值)个小的数,比较两个数组各自第[k/2-1]个数(这两个数之前的数各自有k/2-1个数),对A[k/2-1]B[k/2−1] 中的较小值,最多只会有 A中的(k/2-1)+B中的(k/2−1)≤k−2 个元素比它小,所以两个之中较小的数不可能是{A,B}中第k个小的数。这样每次能排除k/2个数

2. 剑指_4 二维数组查找

剑指_4 二维数组查找
二分法用于有序数列,是每次比较mid和target,看是往mid左边小区间走还是mid右边小区间走
对于二维数组每个维度上都是有序数列,也就是行、列方向上都可以往小走或往大走,就有4个方向,但是二维数组也是有边界的,对于边界上的点并不是4个方向都可以,要保证起始时查看的位置既有小于他的又有大于他的,起始位置就相当于二分查找中的最初的mid
所以查找的起始点在右上角或左下角,右上角时往左走变小,往下走变大

算法和刷题——二分法相关推荐

  1. 图解算法数据结构刷题笔记02

    系列文章目录 图解算法数据结构刷题笔记01 本篇文章目录 系列文章目录 前言 1.剑指 Offer 05. 替换空格 2.剑指 Offer 06. 从尾到头打印链表 3.剑指 Offer 09. 用两 ...

  2. 【C/C++】蓝桥杯算法必刷题(三)目标ICPC铜/蓝桥杯国一

    目录 前言 题解文章汇总 题目传送门:算法必刷题(三) 该题单中第一类考点:二进制 1018.有趣的二进制 1019.[NOIP2006]数列 1020.只能吃土豆的牛牛 该题单中第二类考点:思维 1 ...

  3. 【C/C++】蓝桥杯算法必刷题(一)目标ICPC铜/蓝桥杯国一

    目录 前言 该题单中第一类考点:输入输出 1001.这是一道签到题 1005.乘法表 1006.KiKi学程序设计基础 1007.疫情死亡率 该题单中第二类考点:思维题 1002.排列式 1018.开 ...

  4. 面试算法LeetCode刷题班—BAT面试官带你刷真题、过笔试

    课程名称: <面试算法LeetCode刷题班> --BAT面试官带你刷真题.过笔试 主讲老师: 林老师 BAT资深研发工程师(T7/P8级),致力于搜索引擎及其子系统的研发.迭代与优化,数 ...

  5. 【算法】刷题范围建议 和 代码规范

    文章目录 一.刷题范围建议 二.代码规范 一.刷题范围建议 面试算法 侧重实践 , 工程 ; 并不是所有的算法都适合面试 , 靠背诵解决问题的算法 , 只能解决单个问题没有推广性的算法 , 有简称 , ...

  6. php算法在线刷题,c,算法_每日一道算法:leetcode 刷题碰到的问题。,c,算法 - phpStudy...

    每日一道算法:leetcode 刷题碰到的问题. 这是题目: Given an unsorted array nums, reorder it such that nums[0] < nums[ ...

  7. 子集和问题 算法_LeetCode刷题实战90:子集 II

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  8. python基础刷题_数据结构与算法LeetCode刷题(Python)

    参考资料: 一.链表 1.  链表的必备知识要点(包括基础知识.刷题中使用的STL等知识) 2.  链表逆序(LeetCode 92 ,206. Reverse Linked List 1,2) 3. ...

  9. 算法入门刷题笔记 Day10 - A - 拓扑排序·一 -- D - K-th Path

    写在前面 好久没更新公众号和博客了,因为最近在研究新的方向,所以很少发文. 笔者接触编程只有一年,这一年间主要研究启发式算法在运筹学中的应用.但是由于编程基础薄弱,在进一步研究复杂运筹学问题时发现基础 ...

最新文章

  1. SAP PM入门系列27 - IW29 Display Notifications
  2. LINUX CentOS7安装字体库
  3. 监控系统安装配置文档(Nagios+Cacti+Nconf)
  4. Linux终端程序用c语言实现改变输出的字的颜色
  5. Anaconda创建python虚拟环境
  6. 动态列排序_Excel表格利用函数制作排序器(可依据不同字段、升降序排序)
  7. charles都踩过哪些坑_野路子14年 不如“缠论”1年 收益翻20倍
  8. h5实现一键复制到粘贴板 兼容iOS
  9. PYTHON自动化Day9-发邮件、面向对象、类、私有、继承
  10. winrar 注册码
  11. Oracle 触发器写法
  12. 【Python】模拟登陆并抓取拉勾网信息(selenium+phantomjs)
  13. MySQL课程超级团,值得再提一次。
  14. 华为十年,总结出的12条经验!(作者是一级部门总监,华为副总裁)
  15. 动态规划——1296:开餐馆
  16. 4G手机网络免费开通高清语音VoLTE
  17. 【Foreign】Melancholy [线段树]
  18. http_proxy设置
  19. 三角测量(Triangulation 三角化)与 SVD 求解
  20. 好莱坞的十大故事引擎

热门文章

  1. 【polar码】polar编译码以及SC译码算法的matlab仿真验证,对比不同编码码率的误码率性能
  2. 安全协议设计与分析-32学时 考试复习
  3. 光纤通信系统组成总结及相干光通信基础
  4. python乱码系列1
  5. 百度云盘核心功能需求分析
  6. Qt源码分析之信号和槽机制
  7. 转 Python爬虫实战一之爬取糗事百科段子
  8. Tomcat 如何查看端口
  9. Redis - 消息发布订阅机制
  10. php输入框里的提示文字,input 标签实现输入框带提示文字效果(两种方法)