LeetCode学习笔记:二分搜索

什么是二分搜索?
二分搜索的基本思想是将n个元素分成大致相等的两部分,取nums[n/2]与target做比较,如果target=nums[n/2],则找到target,算法中止;如果target<nums[n/2],则只要在数组nums的左半部分继续搜索target,如果target>nums[n/2],则只要在数组nums的右半部分搜索target。
二分搜索用在基本有序数组中。解二分搜索的题目,重点是具体情况具体分析,在不确定的情况下,尽量使用else if而不是else语句。二分查找的题目,细节最重要!!!
二分搜索模板:

public class Template {
public int search(int[] nums, int target) {
	int low = 0;
	int high = nums.length-1;
	while(low <= high) {
		//防止数组溢出,加快运算速度
		int middle = low + ((high-low)>>1);
		if(nums[middle] == target) {
			return middle;
		}
		else if(nums[middle] > target) {
			high = middle-1;
		}
		else {
			low = middle+1;
		}
	}
	return -1;
}
}

题型一、在旋转有序数组中查找元素
相关题目:33、81、153、154
解题思路:这类题就是将一个本来有序的数组进行部分旋转,使得数组变成部分有序。解这类题的关键是具体情况具体分析,找出哪边有序,哪边无序。这类题的进阶是数组内可包含相同元素,这时可能会出现无法分辨哪一边是有序的,这时可用一个思路:

//由于可以出现相同元素,所以可能会出现左右都无法判断是否为升序的情况
//这时将左边界后移,右边界前移,再继续判断(因为此时middle不为target,
//在middle等于low和high的情况下low和high都可抛弃)
if(nums[low]==nums[middle] && nums[middle]==nums[high]){
	low++;
	high--;
}

例题:

//81
class SearchinRotatedSortedArrayⅡ {
public boolean search(int[] nums, int target) {
	int low = 0;
	int high = nums.length-1;
	while(low <= high) {
		int middle = low + ((high-low)>>1);
		if(nums[middle] == target) {
			return true;
		}
		//若只排除剩一个元素而没找到,则返回-1防止死循环
		else if(low == high) {
			return false;
		}
		//若只排除剩两个元素,此时middle的左边没有元素无法判断是否升序,则直接判断这两个数是否为target
		//可排除数组越界和其他无法预料的错误
		else if(low+1 == high) {
			if(nums[low] == target) {
				return true;
			}
			else if(nums[high] == target) {
				return true;
			}
			else return false;
		}
		//对比33题增加这一句
		//由于可以出现相同元素,所以可能会出现左右都无法判断是否为升序的情况
		//这时将左边界后移,右边界前移,再继续判断(因为此时middle不为target,在middle等于low和high的情况下low和high都可抛弃
		if(nums[low]==nums[middle] && nums[middle]==nums[high]){
			low++;
			high--;
		}
		//若middle左边部分为升序且target在左边部分范围内则在左边寻找
		else if(nums[low]<=nums[middle-1] && target>=nums[low] && target<=nums[middle-1]) {
			high = middle-1;
		}
		//若middle左边部分为升序且target不在左边部分范围内则在右边寻找
		else if(nums[low]<=nums[middle-1] && (target<nums[low] || target>nums[middle-1])){
			low = middle+1;
		}
		//若middle右边部分为升序且target在右边部分范围内则在右边寻找
		else if(nums[middle+1]<=nums[high] && target>=nums[middle+1] && target<=nums[high]) {
			low = middle+1;
		}
		//若middle右边部分为升序且target不在右边部分范围内则在左边寻找
		else if(nums[middle+1]<=nums[high] && (target<nums[middle+1] || target>nums[high])) {
			high = middle-1;
		}
	}
	return false;
}
}

每天一道LeetCode题,冲!!!

题型二、在山峰数组中找出山峰
相关题目:162、852
解题思路:山峰数组是指nums[i]≠nums[i-1]的数组,即相邻元素不相等。山峰的定义是:严格大于左右元素的元素。既然有左右就必定涉及一个数组边界的问题,所以这类题一般都会约定nums[-1] = nums[n] = -∞,即数组的左右边界为无穷小。由以上这些条件,再因为山峰一定在高处,所以可以使用二分查找的方法来不断趋近山峰直到找到山峰。我们可以将middle所在的值与middle+1所在的值进行比较,若middle>middle+1,则说明山峰一定在middle这边,此时high=middle;若middle<middle+1,说明山峰一定在middle+1这边,此时low=middle+1。
例题:

//162
//由题意:假设 nums[-1] = nums[n] = -∞ 。说明数组边界外是无穷小的
//说明只要往相邻的数中的大的数走,就一定能找到峰值
class FindPeakElement {
    public int findPeakElement(int[] nums) {
    	int low = 0;
    	int high = nums.length-1;
    	while(low <= high) {
    		int middle = low+((high-low)>>1);
    		//当low指针和high指针相遇时找到峰值
    		if(low == high) {
    			return low;
    		}
    		//若middle比middle右边的值要大,说明峰值一定在middle这边
    		else if(nums[middle] > nums[middle+1]) {
    			high = middle;
    		}
    		//若middle比middle右边的值要小,说明峰值一定在middle右边
    		else if(nums[middle] < nums[middle+1]) {
    			low = middle+1;
    		}
    	}
    	return -1;
    }
}

每天一道LeetCode题,冲!!!

题型三、最大值最小化问题
相关题目:410、875、1011、1283
解题思路:这类题目的一般问法是:在满足某个阈值限制的情况下,找出结果中的最小值。结果与条件一般是互斥的,即一个变大另一个就会变小。满足阈值限制的结果有很多个,要求找出最小的那个。(注意:最小的那个结果并不等同于最接近于阈值的那个,最接近于阈值的结果可能有多个)这类问题的难点于关键在于找出与结果有关的能够确定区间的变量,再用二分查找不断地逼近这个变量的正确值,最后得出结果。
这类题还有两个需要注意的点:

  1. while循环条件中的low一般不等于high,因为low等于high时一般是结果需要跳出循环并返回
  2. 在处理区间的扩大缩小时有一个两值相等时如何处理的问题,此时需要详细分析

例题:

//875
//在H小时内以最小速度K吃掉的香蕉数一定在[low,high]之间
//low指的是piles中香蕉的总数,high指的是piles中香蕉数最多的堆乘以时间H
//low是最好的情况,此时每堆香蕉数都为K,速度K能够完全发挥出来
//high是最差的情况,此时时间H等于堆数piles.length,且堆中有某个堆的香蕉数远远大于其他堆,速度K发挥不完全
//有了[low,high]这个区间之后,我们就可以指定一个middle,相当于猜一个数为H小时内以最小速度K吃掉的香蕉数
//再用这个middle除以时间H得到假定的最小速度tempK,然后遍历整个piles,用每个香蕉堆数除以tempK,计算总和,得到时间tempH
//用tempH与真正时间H比较,若tempH<H,说明时间小了,速度大了,则middle大了,所以将high=middle,反之亦然
//使用long是因为数据量大,防止数据溢出
class KokoEatingBananas {
    public int minEatingSpeed(int[] piles, int h) {
    	//piles中香蕉的总数
    	long low = 0;
    	//piles中香蕉数最多的堆乘以时间H
    	long high = piles[0];
    	for(long num : piles) {
    		low += num;
    		if(high < num) {
    			high = num;
    		}
    	}
    	high *= h;
    	//若出现香蕉总数比时间还要小的情况,则直接返回最小正整数1
    	if(low < h) {
    		return 1;
    	}
    	//在H小时内以最小速度K吃掉的香蕉数一定在[low,high]之间
    	//这类题使用二分的时候一般不能low=high,因为low=high时往往是结果,防止死循环
    	while(low < high) {
    		//猜一个数middle为H小时内以最小速度K吃掉的香蕉数
    		long middle = low+((high-low)>>1);
    		//假定的最小速度
    		long tempK = middle / h;
    		//假定的时间
    		int tempH = 0;
    		//用每个香蕉堆数除以tempK,计算总和,得到时间tempH
    		for(int num : piles) {
    			if(num%tempK != 0) {
    				tempH += (num/tempK+1);
    			}
    			else {
    				tempH += (num/tempK);
    			}
    		}
    		//若tempH>H,说明时间大了,速度小了,则middle小了,需要变大
    		if(tempH > h) {
    			low = middle+1;
    		}
    		//若tempH<H,说明时间小了,速度大了,则middle大了,需要缩小
    		//若tempH=H,此时虽然时间正确,但不一定是最小速度,还是需要将middle进一步缩小
    		else {
    			high = middle;
    		}
    	}
    	return (int)(low/h);
    }
}

每天一道LeetCode题,冲!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值