【数据结构与算法】二分查找法合集
搜索插入位置
LeetCode:搜索插入位置
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例:
输入: [1,3,5,6], 5
输出: 2
思想:
正常的二分查找
代码:
递归方式
class Solution {
public int searchInsert(int[] nums, int target) {
return binarySearch(0,nums.length-1,nums,target);
}
private int binarySearch(int low, int high, int[] nums, int target){
if(high<low){
return low;
}
int mid = (high + low)/2;
if(nums[mid] > target){
return binarySearch(low,mid - 1,nums,target);
}else if(nums[mid] < target){
return binarySearch(mid + 1, high,nums,target);
}else{
return mid;
}
}
}
循环方式
public int binarySearch(int[] nums, int key) {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (nums[m] == key) {
return m;
} else if (nums[m] > key) {
h = m - 1;
} else {
l = m + 1;
}
}
return -1;
}
x的平方根
LeetCode:X的平方根
题目描述:
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
思想:
二分查找的变种。注意不要用m * m == x来判断,m * m会越界,要用m==x/m来做判断条件。
代码:
class Solution {
public int mySqrt(int x) {
if(x<=1)
return x;
int low=0;
int high=x;
int m=x;
while(high>=low){
m=low+(high-low)/2;
if(m==x/m){
return m;
}else if(m>x/m){
high=m-1;
}else if(m<x/m){
low=m+1;
}
}
return high;
}
}
寻找比目标字母大的最小字母
LeetCode:寻找比目标字母大的最小字母
题目描述:
给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。
在比较时,字母是依序循环出现的。举个例子:
如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'
示例:
输入:
letters = ["c", "f", "j"]
target = "a"
输出: "c"
输入:
letters = ["c", "f", "j"]
target = "c"
输出: "f"
输入:
letters = ["c", "f", "j"]
target = "d"
输出: "f"
思想:
注意有多个重复元素的情况,要把<和==的情况合在一起进行处理,得到右边界。
代码:
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
int l=0,h=letters.length-1,mid;
while(h>=l){
mid = (h+l)/2;
if(letters[mid]>target)
h=mid-1;
else
l=mid+1;
}
return letters[l%letters.length];
}
}
有序数组的单一元素
LeetCode:有序数组的单一元素
题目描述:
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例:
输入: [3,3,7,7,10,11,11]
输出: 10
思想:
这题的关键在于,mid每次都要取偶数,再判断nums[mid]==nums[mid+1],即可得知结果在左半边还是右半边。
代码:
class Solution {
public int singleNonDuplicate(int[] nums) {
int low=0,high=nums.length-1,mid;
while(high>low){
mid = low+(high-low)/2;
if(mid%2==1) mid--;
if(nums[mid]==nums[mid+1])
low = mid+2;
else
high = mid;
}
return nums[low];
}
}
第一个错误版本
LeetCode:第一个错误版本
题目描述:
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
思想:
二分查找的变种,要找true的最左边。判断条件比较特殊,不能生搬硬套3.寻找比目标字母大的最小字母一题中的规律。
代码:
/* The isBadVersion API is defined in the parent class VersionControl.
boolean isBadVersion(int version); */
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int low=1,high=n,mid;
while(high>low){
mid = low+(high-low)/2;
if(isBadVersion(mid)){
high = mid;
}else{
low = mid+1;
}
}
return low;
}
}
寻找旋转排序数组中的最小值
LeetCode:寻找旋转排序数组中的最小值
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例:
输入: [4,5,6,7,0,1,2]
输出: 0
思想:
二分查找变种,主要是判断条件 nums[mid]>=nums[0] 需要细细琢磨。
代码:
class Solution {
public int findMin(int[] nums) {
int low=0,high=nums.length-1,mid;
while(high>=low){
mid = low+(high-low)/2;
if(nums[mid]>=nums[0]){
low = mid+1;
}else{
high = mid -1;
}
}
return nums[low%nums.length];
}
}
二分查找标准及变种
标准形式:
public int binarySearch(int[] nums, int key) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[m] == key) {
return mid;
} else if (nums[mid] > key) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
总结:
- 当判断条件使用“nums[i]<=target”时,即将“<”和“==”合在一起执行 low = mid + 1, 最后结果 high 为“含重复target最右边”下标,low 为“比target大的最小的值”;
- 当判断条件使用“nums[i]>=target”,即将“>”和“==”合在一起执行 high = mid - 1,最后结果 high 为“比target小的最大的值”, low 为“含重复target最左边”下标。
public static void binarySearchRightRepeat(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] > target) {
high = mid - 1;
} else if (nums[mid] <= target) {
low = mid + 1;
}
}
System.out.println("low:"+low);//比target大的最小值
System.out.println("high:"+high);//与target相等的最右边的值
}
public static void binarySearchLeftRepeat(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (nums[mid] >= target) {
high = mid - 1;
} else if (nums[mid] < target) {
low = mid + 1;
}
}
System.out.println("low:"+low);//与target相等的最左边的值
System.out.println("high:"+high);//比target小的最大值
}
- 使用变种“<=”+"low=mid"时,(要将 while 条件改为 low<high),最后结果 low 和 high 都为“含重复target最右边”下标;
- 使用变种“>=”+"high=mid"时,(要将 while 条件改为 low<high),最后结果 low 和 high 都为“含重复target最左边”下标;