leecode-二分查找问题

二分查找

  1. 每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为 O(n) 的数组,二分查找的时间复杂度为 O(log n)。

  2. 可以用更加数学的方式定义二分查找。给定一个在 [a, b] 区间内的单调函数 f (x),若f (a) 和 f (b) 正负性相反,那么必定存在一个解 c,使得 f (c) = 0。在上个例子中,f (x) 是离散函数f (x) = x +2,查找 4 是否存在等价于求 f (x) −4 = 0 是否有离散解。因为 f (1) −4 = 3-4 = 1 < 0、f (5) − 4 = 7- 4 = 3 > 0,且函数在区间内单调递增,因此我们可以利用二分查找求解。如果最后二分到了不能再分的情况,如只剩一个数字,且剩余区间里不存在满足条件的解,则说明不存在离散解,即 4 不在这个数组内。

  3. 按照个人习惯,区间的选定为左闭右开

求开方

题目:给定一个非负整数,求它的开方,向下取整。

Input: 8
Output: 2
一、二分查找法
化为数学公式为满足x2<8的最大整数。可以对[0,8)区间的数进行二分,然后判断轴点的平方与8的大小关系.
若大于8,取[lo,mi),若小于或等于8,取[mi,hi),经过不断的二分。最后返回lo即可。这里注意为了防止int超上界,使用long储存数据

public int mySqrt(int x) {
	long lo = 0;
	long hi = x;
	while (lo<hi){
		long mi =(hi + lo) >> 1;
		if (x < mi * mi){
			hi = mi;
		} else {
			lo = mi+1;
		}
	}
	return (int)--lo;
}

这里会发现在边界处,即0、1处有问题,直接把特例列出来即可

public int mySqrt(int x) {
	if (x == 1 || x == 0) return x;
	long lo = 0;
	long hi = x;
	while (lo<hi){
		long mi =(hi + lo) >> 1;
		if (x < mi * mi){
			hi = mi;
		} else {
			lo = mi+1;
		}
	}
	return (int)--lo;
}

二、袖珍计算器
通过转化为其他计算方法得到,一般不使用这个方法

public int mySqrt(int x) {
	if (x == 0) {
		return 0;
	}
	int ans = (int)Math.exp(0.5 * Math.log(x));
	return (long)(ans + 1) * (ans + 1) <= x ? ans + 1 : ans;
}

三、牛顿方法
化为数学公式,即求F(x)= x2 - C = 0时,x的解,根据微积分化为,xi =0.5(x0+c/x0),然后比较x0与xi的大小,直至满足要求

public int mySqrt4(int x) {
	if (x == 0) {
		return 0;
	}
	double C = x, x0 = x;
	while (true) {
		double xi = 0.5 * (x0 + C / x0);
		if (Math.abs(x0 - xi) < 1e-7) {
			break;
		}
		x0 = xi;
	}
	return (int) x0;
}

查找区间

有序数组中值的位置

给定一个增序的整数数组和一个值,查找该值第一次和最后一次出现的位置

Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

一、二分查找法
经常使用的二分查找,返回的是不大于查找值的最大位置,我们用find(x)、find(x-1)+1找到的便是最后和最初出现的位置
特殊情况:数组中不含有该元素,单独列出来

public int[] searchRange(int[] nums, int target) {
	long tar = target;
	int a = find(nums,tar-1)+1;
	int b = find(nums,tar);
	if (a == nums.length || nums[a] != target){
		return new int[]{-1,-1};
	}
	return new int[]{a,b};
}
public int find(int[] nums,long e){
	int lo = 0;
	int hi = nums.length;
	while (lo < hi){
		int mi =  lo + ((hi - lo) >> 1);
		//取得两者的中点
		if (e < nums[mi]){
			//mi处值大于e
			hi = mi;
		} else {
			lo = mi+1;
		}
	}
	return --lo;
}

有序数组中单一元素

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数

输入: [1,1,2,3,3,4,4,8,8]
输出: 2

一、二分查找法
数学上,简化为由一系列点组成的函数,其中只有一个是单独的,我们利用二分查找的办法,轴处的mi(这里不是比较大小)而是判断(mi-0)%2 == 0,以及与左一位和右一位作比较,这样共有四种情况,判断即可,最后都不满足就是找到这个数,返回即可

public int singleNonDuplicate1(int[] nums) {
	int lo = 0;
	int hi = nums.length - 1;
	while (lo < hi) {
		int mid = lo + (hi - lo) / 2;
		Boolean halvesAreEven = (hi - mid) % 2 == 0;
		if (nums[mid + 1] == nums[mid]) {
			if (halvesAreEven) {
				lo = mid + 2;
			} else {
				hi = mid - 1;
			}
		} else if (nums[mid - 1] == nums[mid]) {
			if (halvesAreEven) {
				hi = mid - 2;
			} else {
				lo = mid + 1;
			}
		} else {
			return nums[mid];
		}
	}
	return nums[lo];
}

二、二分查找(只对偶数索引进行判断)
沿用上面的思路,每次取得的轴点为偶数(奇数的话减一)(对应的是奇数个元素),判断其与后一位的数是否相等,相等的话,独数在后面,不等的话独数在前面,最后返回lo处元素即可

public int singleNonDuplicate2(int[] nums) {
	int lo = 0;
	int hi = nums.length - 1;
	while (lo < hi) {
		int mid = lo + (hi - lo) / 2;
		if (mid % 2 == 1) mid--;
		if (nums[mid] == nums[mid + 1]) {
			lo = mid + 2;
		} else {
			hi = mid;
		}
	}
	return nums[lo];
}

旋转数组查找数字

旋转排序数组判断值是否存在

一个原本增序的数组被首尾相连后按某个位置断开(如 [1,2,2,3,4,5] → [2,3,4,5,1,2],在第一位和第二位断开),我们称其为旋转数组。给定一个值,判断这个值是否存在于这个旋转数组中。

Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true

一、二分查找法
数学上,将其看作一个分段区间,其中区间的左边界要高于或等于右边界。进行二分查找时,轴点mi处数值与左边界作比较
但大于时,其位于左半边,小于时,其位于右半边;相等时比较特殊,把左端点右移一位,继续进行二分查找

public Boolean search(int[] nums, int target) {
	if (nums == null || nums.length == 0) {
		return false;
	}
	int lo = 0;
	int hi = nums.length-1;
	while (lo < hi){
		int mi = (lo+hi)/2;
		if (nums[mi] == target){
			return true;
		}
		if (nums[mi] == nums[hi]){
			--hi;
		} else if (nums[mi] < nums[hi]){
			if (target > nums[mi] && target <= nums[hi]){
				lo = mi+1;
			} else {
				hi = mi-1;
			}
		} else {
			if (target < nums[mi] && target >= nums[lo]){
				hi = mi-1;
			} else {
				lo = mi + 1;
			}
		}
	}
	return false;
}

旋转排序数组中最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。请找出其中最小的元素。注意数组中可能存在重复的元素。

输入: [1,3,5]
输出: 1

一、二分查找法
数学上,将其看作一个分段区间,其中区间的左边界要高于或等于右边界。进行二分查找时,轴点mi处数值与左边界作比较
但大于时,其位于左半边,小于时,其位于右半边;相等时比较特殊,把左端点右移一位,继续进行二分查找

public int findMin1(int[] nums) {
	int low = 0;
	int high = nums.length - 1;
	while (low < high) {
		int pivot = low + (high - low) / 2;
		if (nums[pivot] < nums[high]) {
			high = pivot;
		} else if (nums[pivot] > nums[high]) {
			low = pivot + 1;
		} else {
			high -= 1;
		}
	}
	return nums[low];
}

两个数组同时进行二分搜索(还未写)

posted @ 2020-08-29 17:43  流沙uiui  阅读(299)  评论(0编辑  收藏  举报