【常见面试算法题】之二分搜索
来源:互联网 发布:linux c 串口中断 编辑:程序博客网 时间:2024/06/01 19:43
算法概要
在计算机科学中,二分搜索(英语:binary search),也称折半搜索(英语:half-interval search)[1]、对数搜索(英语:logarithmic search)[2],是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
时间复杂度
折半搜索每次把搜索区域减少一半,时间复杂度为 O(log n)。(n代表集合中元素的个数)
空间复杂度
虽以递归形式定义,但是尾递归,可改写为循环。空间复杂度为O(1).
实现(java)
循环式二分搜索
public static int rank(int key, int[] a) { int low = 0; int high = a.length - 1;// int mid = (low + high) / 2;// int mid = low + (high - low) / 2;//这行代码必须放在循环内部 while (low <= high) {// int mid = low + (high - low) / 2; //这样会溢出 int mid = low + ((high - low) >> 1); //不要忘记加括号,还有>> 1不是2 //最好最后检测等于这个情况 if (key == a[mid]) { return mid; } else if (key < a[mid]) { high = mid - 1; }else { low = mid + 1; } } return -1; }
递归式二分搜索
private static int binarySearch(int[] numbers, int start, int end, int key) { while (start <= end) { int mid = start + ((end - start) >> 1); if (key < numbers[mid]) { return binarySearch(numbers, start, mid - 1, key); } else if (key > numbers[mid]) { return binarySearch(numbers, mid + 1, end, key); } else { return mid; } } return -1; }
应用场景:
- 有序序列中查找一个数,lgN
- 并不一定要在有序序列中才能得到,只要二分之后,淘汰掉一半,只需要处理另外一半。
二分搜索考察点
边界条件的界限
题目变化
- 有没有重复元素
- 判断条件不同
- 要求返回的内容不同
常见题目
1. 求数组局部最小值位置
定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为N(N>1)时,如果arr[0]
public class Solution { public int getLessIndex(int[] arr) { if (arr == null || arr.length <= 0) { return -1; } //arr长度为1时,arr[0]是局部最小 if (arr.length == 1) { return 0; } //arr的长度为N(N>1)时,如果arr[0]<arr[1],那么arr[0]是局部最小 if (arr[0] < arr[1]) { return 0; } //arr的长度为N(N>1)时,如果arr[N-1]<arr[N-2],那么arr[N-1]是局部最小 int N = arr.length; if (arr[N-1] < arr[N-2]) { return N-1; } int lo = 1, hi = N - 2; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (arr[mid] > arr[mid-1]) { hi = mid - 1; } else if (arr[mid] > arr[mid+1]) { lo = mid + 1; } else { return mid; } } return lo; }}
2. 元素最左出现的位置
对于一个有序数组arr,再给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
给定一个数组arr及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。
思路:
由于数组有序,考虑用二分搜索法快速定位到数组中值为num位置处,找到后num后,因为数组有序递增,在num左边继续遍历,找到最左边的num位置
public int findPos(int[] arr, int n, int num) { if(arr == null || arr.length == 1){ return -1; } int res = -1; int left = 0; int right = arr.length - 1; int mid = 0; while(left <= right){ mid = (left + right) / 2; if(arr[mid] > num){ right = mid - 1; }else if(arr[mid] < num){ left = mid + 1; }else {//arr[mid] == num res = mid; //因为需要寻找最左边的,而数组也是有序的,因此,还需要往mid左边寻找最左的一个num值位置 right = mid -1; } } return res; }
3. 循环有序数组求最小值
对于一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组[1,2,3,3,4],是有序循环数组,[4,1,2,3,3]也是。
给定数组arr及它的大小n,请返回最小值。
测试样例:
[4,1,2,3,3],5
返回:1
import java.util.ArrayList;//已知数组的旋转 求最小值//特殊用例: 数组无旋转 2 . 大小为0 3. 元素有小于0//方法一: 取巧的方式:遍历数组//方法二: 用二分查找public class Solution { public int minNumberInRotateArray(int [] array) { if (array == null || array.length <= 0) { return 0; } int lo = 0, hi = array.length - 1; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (array[mid] > array[hi]) { lo = mid + 1; } else if (array[mid] < array[hi]) { hi = mid; } else { hi --; } } return array[lo]; }}
4. 最左原位
有一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
给定有序数组arr及它的大小n,请返回所求值。
测试样例:
[-1,0,2,3],4
返回:2
public int findPos(int[] arr, int n, int num) { if (arr == null || arr.length <= 0) { return -1; } int lo = 0, hi = n - 1; int res = -1; while (lo <= hi) { if (arr[lo] > lo || arr[hi] < hi) { break; } int mid = lo + ((hi - lo) >> 1); if (arr[mid] > mid) { hi = mid - 1; } else if (arr[mid] < mid) { lo = mid + 1; } else { res = mid; hi = mid - 1; } } return res; }
5. 完全二叉树计数
给定一棵完全二叉树的根节点root,返回这棵树的节点个数。如果完全二叉树的节点数为N,请实现时间复杂度低于O(N)的解法。
给定树的根结点root,请返回树的大小。
思路:
(1)首先一直遍历到根节点的左子树的最左边,得到树的高度high;
(2)再遍历根节点的右子树的最左边,得到右子树的高度rightHigh;
(3)如果high==rightHigh,表示根节点的左子树为满二叉树,高度为high,此时左子树可以直接根据满二叉树的性质求出节点数量,再对右子树递归遍历;
(4)如果high!=rightHigh,表示根节点的右子树为满二叉树,高度为high - 1,此时右子树可以直接根据满二叉树的性质求出节点数量,再对左子树递归遍历;
public int treeCount(TreeNode root) { if (root == null) { return 0; } int height = 0; TreeNode node = root; while (node != null) { height++; node = node.left; } int rightHeight = 0; node = root; while (node != null) { rightHeight++; node = node.right; } if (height == rightHeight) { return (int) (Math.pow(2, height - 1) + treeCount(root.right)); } else { return (int) (Math.pow(2, rightHeight - 1) + treeCount(root.left)); } }
6. 快速N次幂
关于矩阵快速幂的应用
请见 魔力手环–2017网易编程应用题
public int pow(int a, int b) { int res = 1; while (b > 0) { if ((b & 1) == 1) { res *= a; } a *= a; b >>= 1; } return res; }
更多文章
- 二分搜索算法
- 有序数组的查找
- 程序员编程艺术第二十五章:Jon Bentley:90%无法正确实现二分查找
- 二分查找(Binary Search) 常见问题解决方法总结
- 二分算法入门——二分查找
- 【常用算法思路分析系列】与二分搜索相关高频题
- 【常见面试算法题】之二分搜索
- 面试复习-------算法与数据结构------二分搜索
- 算法之二分搜索法
- 搜索算法之二分查找
- 经典算法之二分搜索
- 查找算法之二分搜索
- 常见算法之二分查找
- 算法基础知识科普:8大搜索算法之二分搜索
- 常见算法基础题思路简析(四)-二分搜索篇
- 分治算法之二分搜索--Binary
- 搜索算法之二分查找法
- 经典算法之二分搜索技术
- 经典算法之二分搜索技术
- 常见查找算法之—二分查找
- 笔试面试----常见算法题
- 常见的面试算法题
- 面试常见题(非算法)
- 常见面试算法题整理
- RSA非对称加密方案
- maven的pom.xml的配置文件说明
- Android Studio 集成git上传项目至GitHub
- 普及练习场之排序Ex
- java 下载文件
- 【常见面试算法题】之二分搜索
- 实现控件拖动
- LeetCode 34 Search for a Range
- hadoop常用算法在spark中实现
- GCC Compile and Link options
- 【video】主流流媒体服务器介绍
- 获取ip地址
- jdk1.8中ConcurrentHashMap的实现原理
- 反射(基础知识)