• 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];
    }
    

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

  • 相关阅读:
    SlideShowExtender制作相册
    Response.Redirect(),Server.Transfer(),Server.Execute()的区别
    虚方法,抽象类,多态性
    gridview获取当前行索引的方法
    AutoQueryTextBox(AjaxPro.dll)非常值得研究的javascript代码
    abstract & virtual & override & new比较(转)
    Asp.net技巧:gridview获取当前行索引的方法
    js 获取浏览器高度和宽度值
    深入理解abstract class和interface
    c++ 静态数据成员和静态成员函数
  • 原文地址:https://www.cnblogs.com/suit000001/p/13582901.html
Copyright © 2020-2023  润新知