算法知识-二分查找
Posted 朱芒格
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法知识-二分查找相关的知识,希望对你有一定的参考价值。
文章内容,整理自LeetCode官网题解
二分查找
—
基础介绍
二分查找也称折半查找「Binary Search」,它是一种效率较高的查找方法,可以在数据规模的对数时间复杂度内完成查找。二分查找主要应用于数组结构,是因为数组具有随机访问的特点。
原理:
将数组分为三部分,依次是中值前、中值、中值后;将要查找的值和数组的中值进行比较,若小于中值则在中值前面找,若大于中值则在中值后面找,等于中值时直接返回。然后依次是一个递归过程,将前半部分或者后半部分继续分解为三部分
应用场景
查找给定的目标值在数组中是否存在
查找给定的目标值在数据中的起始点
二分查找算法在完全二叉树上的应用
二分查找
—
二分查找
LeetCode704
public int search(int[] array, int target) {
int mid, left = 0;
int right = array.length - 1;
while (left <= right) {
mid = left + ((right - left) / 2);
if (array[mid] == target) {
return mid;
}
if (array[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
注意事项:
循环的条件是 while(left <= right),所以当left==right时,循环也会查找下去
mid = (left + right)/2; 若left + right > Integer.MAX_VALUE时,此时会导致数据溢出,可以采用的写法为:
mid = left + ((right-left)/2)
mid = left + ((right - left) >> 1);右移一位,相当于/2,右移n位相当于 /2的n次方
二分查找
—
左右区间
LeetCode34
public int[] searchRange(int[] nums, int target) {
int left = binarySearchLeft(nums, target);
int right = binarySearchRight(nums, target) - 1;
boolean check = left <= right
&& right <= nums.length - 1
&& nums[left] == target
&& nums[right] == target;
if (check) {
return new int[]{left, right};
} else {
return new int[]{-1, -1};
}
}
public int binarySearchLeft(int[] nums, int target) {
int mid, left = 0;
int right = nums.length - 1;
int ans = nums.length;
while (left <= right) {
mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
public int binarySearchRight(int[] nums, int target) {
int mid, left = 0;
int right = nums.length - 1;
int ans = nums.length;
while (left <= right) {
mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
ans = mid;
} else {
left = mid + 1;
}
}
return ans;
}
注意事项:
left的边界,第一个>=target的下标
right的边界,第一个>target的下标,然后减一
遇到这道题的第一思路,可能就是先利用二分查找,在数组中找到target数值。然后
向左遍历,看是否存在相同的target
向右遍历,看是否存在相同的target
这个想法来源于,我们最熟悉的二分查找就是用来查找某个target,如果存在,就直接返回。二分查找,最主要的特性是折半,不是仅局限于定位某个数值是否存在,还可以利用折半的特性,去搜寻边界
二分查找
—
完全二叉树
LeetCode222
给你一颗完全二叉树的根节点root,求出该树的节点个数。
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为h层,则该层包含1~2^h个节点
1 => 1 ~ 2^0 第0层
2 3 => 1 ~ 2^1 第1层
4 5 6 7 => 1 ~ 2^2 第2层
对于任意二叉树,都可以通过广度优先搜索或深度优先搜索计算节点个数,时间复杂度和空间复杂度都是
public int countNodes(TreeNode root){
if(root==null){
return 0;
}
return countNodes(root.left) + countNodes(root.right) + 1;
}
但是这里我们尝试用二分查询,来解这道题。规定根结点位于第0层,完全二叉树的最大层数为h。根据完全二叉树的特性可知,完全二叉树的最左边的节点一定位于最底层,因此从根结点出发,每次访问左子节点,直到遇到叶子节点,该叶子节点即为完全二叉树的最左边的节点,经过的路径长度即为最大层数h
当
当最底层包含
因此对于最大层数为
具体做法是,根据节点个数范围的上下界得到当前需要判断的节点个数
如何判断第
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
// 遍历左子树,得到高度
int level = 0;
TreeNode node = root;
while (node.left != null) {
level++;
node = node.left;
}
// 获得用于二分查找的开闭区间
int mid = 0;
int left = 1 << level;
int right = (1 << (level + 1)) - 1;
while (left < right) {
mid = (right - left + 1) / 2 + left;
if (exists(root, level, mid)) {
left = mid;
} else {
right = mid - 1;
}
}
return mid;
}
public boolean exists(TreeNode root, int level, int k) {
// 类似一个掩码,用来提取第几位数字
int bits = 1 << (level - 1);
TreeNode node = root;
while (node != null && bits > 0) {
if ((bits & k) == 0) {
node = node.left;
} else {
node = node.right;
}
// 掩码右移
bits >>= 1;
}
return node != null;
}
算法解释 bits :
对于普通二叉树来说,使用递归的解法,就已经适用了。但是本题是完全二叉树,有一些特殊的性质,因此可以利用完全二叉树的特性来计算节点个数
bits为什么一开始是 1<< (level -1),即2^(h-1)?因为表示最底层编码的二进制一共需要h+1位。2^(h-1)二进制为:0100...0 (第一个是0,一个1,h-1个0,一共h+1位),用掩码来跳过k的第一位,只看k的后h位。非常巧妙的是,不看最底层结点的编号最高位的1,剩下数字刚好就是二叉树从根到最底层节点左0右1的路径
算法解释 补零 :
计算机存储数值的时候,某一个类型在内存中所占的大小是固定的,int就是32位「4字节」、long就是64位「8字节」,所以如果二进制本身长度不够这些位的话,前面就要补0
十进制2转换成二进制是1 0,如果是用int存的话,前面就要补充30个0,如下:
0000 0000 0000 0000 0000 0000 0000 0010
算法解释 & :
与运算符,运算规则如下:
0 & 0 = 0; 0 & 1 = 0; 1 & 1 = 1。即两个同时为1,结果为1,否则为0
例如:3&5
十进制3转换为二进制的3:0000 0011
十进制5转换为二进制的5:0000 0101
即:3&5 = 1
以上是关于算法知识-二分查找的主要内容,如果未能解决你的问题,请参考以下文章
算法入门 02二分查找(简单 - 第四题)LeetCode 167
⭐算法入门⭐《二分枚举》中等02 —— LeetCode 面试题 10.09. 排序矩阵查找