二分查找
普通二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
算法思路:
初始化左指针 left = 0, 右指针 right = nums.length - 1。 则其中点 pivot = left + ((right - left)>>1).
如果 target = nums[pivot],则找到了目标元素,返回 pivot。
如果 target < nums[pivot],由于在顺序数组中,则target不可能在pivot的右侧,则在左侧继续搜索 right = pivot - 1。
如果 target > nums[pivot],由于在顺序数组中,则target不可能在pivot的左侧,则在右侧继续搜索 left = pivot + 1。
代码如下:
class Solution {
public int search(int[] nums, int target) {
int pivot, left = 0, right = nums.length - 1;
while (left <= right) {
pivot = left + (right - left) / 2;
if (nums[pivot] == target) return pivot;
if (target < nums[pivot]) right = pivot - 1;
else left = pivot + 1;
}
return -1;
}
}
由于每次上述都将数组的长度变为原来的一半,则其时间复杂度为O(logn)
模糊二分查找
查找第一个等于给定值的元素的位置
代码如下:
private static int search(int[] data,int value){
if (data == null)return -1;
if(data.length <= 0)return -1;
if (data.length == 1){
if(data[0] == value)return 0;
else return -1;
}
int start = 0;
int end = data.length - 1;
while (end >= start){
int middle = (end - start)/2 + start;
if(data[middle] == value){
if(middle == 0)return middle;
if(data[middle - 1] != value)return middle;
end = middle - 1;
}else if(data[middle] > value){
end = middle - 1;
}else if(data[middle] < value){
start = middle + 1;
}
}
return -1;
}
查找最后一个等于指定元素的值的位置
代码如下:
private static int search(int[] data,int value){
if(data == null)return -1;
if(data.length <= 0)return -1;
if (data.length == 1){
if(data[0] == value)return 0;
else return -1;
}
int start = 0;
int end = data.length - 1;
while (end >= start){
int middle = (end - start)/2+start;
if(data[middle] == value){
if(middle == data.length - 1)return middle;
if(data[middle+1]!=value)return middle;
start = middle + 1;
}else if(data[middle] > value){
end = middle - 1;
}else{
start = middle + 1;
}
}
return -1;
}
查找第一个大于等于指定值的元素的位置
代码如下:
private static int search(int[] data,int value){
if(data == null)return -1;
if(data.length <=0)return -1;
if(data.length == 1){
if(data[0] >= value)return 0;
else return -1;
}
int start = 0;
int end = data.length - 1;
while (end >= start){
int middle = (end - start)/2 + start;
if(data[middle] >= value){
if(middle == 0)return middle;
if(data[middle - 1] < value)return middle;
end = middle - 1;
}else{
start = middle + 1;
}
}
return -1;
}
查找最后一个小于等于指定值的元素的位置
代码如下:
private static int search(int[] data,int value){
if(data == null)return -1;
if(data.length <= 0)return -1;
if(data.length == 1){
if(data[0] <= value)return 0;
else return -1;
}
int start = 0;
int end = data.length - 1;
while (end >= start){
int middle = (end - start)/2 + start;
if(data[middle] <= value){
if (middle == data.length-1)return middle;
if(data[middle+1] > value)return middle;
start = middle + 1;
}else{
end = middle - 1;
}
}
return -1;
}
二分查找的应用
x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
思路:
先设置左边界 l ,右边界 r;其中点为 mid = (r - l)/2 + l;
当mid * mid > x 时,说明所找的的数在 mid 的左边,即 r = mid - 1; 当mid * mid < x 时,说明所找的数在 mid 右边,即 l = mid + 1,同时记录mid值,即 ans = mid,保存可能的结果;当 mid * mid == x时,直接返回。
代码如下:
class Solution {
public int mySqrt(int x) {
int l = 0;
int r = x;
int ans = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
long res = (long)mid * mid;
if (res == x) {
return mid;
}else if(res > x){
r = mid - 1;
}else{
l = mid + 1;
ans = mid;
}
}
return ans;
}
}
0~n-1 中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
从上面的例子可以看出,二分法就是要找出划分的条件将数组一分为二。通过题目,我们可以发现,当数组的下标与数组值满足 index = nums[index] 时,其要找的数字在index右边。
代码如下:
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] == m) i = m + 1;
else j = m - 1;
}
return i;
}
}
旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
解析来源这里:

循环二分: 设置 i,j指针分别指向 numbers 数组左右两端,m = (i + j) // 2 为每次二分的中点("//"代表向下取整除法,因此恒有 i <= m < j),可分为以下三种情况:
当 numbers[m] > numbers[j]时: m 一定在 左排序数组 中,即旋转点 x 一定在 [m + 1, j]闭区间内,因此执行 i = m + 1;
当numbers[m] < numbers[j] 时: m 一定在 右排序数组 中,即旋转点 x 一定在[i, m] 闭区间内,因此执行 j = m;
当 numbers[m] == numbers[j] 时: 无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在 [i, m]还是 [m + 1, j]区间中。解决方案: 执行 j = j - 1缩小判断范围
代码如下:
class Solution {
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) i = m + 1;
else if (numbers[m] < numbers[j]) j = m;
else j--;
}
return numbers[i];
}
}
快速幂
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
示例2:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
可知:x^n = x^(n/2) * x^(n/2)
当n为偶数时,x^n = x^(n/2) * x^(n/2)
当n为奇数时,x^n = x*x^(n/2) * x^(n/2).
通过循环 x = x^2 操作,每次把幂从 n 降至 n/2 ,直至将幂降为 0.可以看出它的时间复杂度为O(logn)。
代码如下:
class Solution {
public double myPow(double x, int n) {
if(x == 0) return 0;
long b = n;
double res = 1.0;
if(b < 0) {
x = 1 / x;
b = -b;
}
while(b > 0) {//b为奇数时b&1 == 1;b为偶数时b&1 == 0
if((b & 1) == 1) res *= x;
x *= x;
b >>= 1;//等同于 b /= 2
}
return res;
}
}
如果看不懂二分法解释可以看一下二进制的解释