【常见面试算法题】之二分搜索

来源:互联网 发布: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;    }

应用场景:

  1. 有序序列中查找一个数,lgN
  2. 并不一定要在有序序列中才能得到,只要二分之后,淘汰掉一半,只需要处理另外一半。

二分搜索考察点

边界条件的界限

题目变化

  1. 有没有重复元素
  2. 判断条件不同
  3. 要求返回的内容不同

常见题目

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) 常见问题解决方法总结
  • 二分算法入门——二分查找
  • 【常用算法思路分析系列】与二分搜索相关高频题
1 0
原创粉丝点击