二分法示意图
算法:二分法
学习链接:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
相关题目练习:1.leetcode 35题搜索插入35. 搜索插入位置 - 力扣(LeetCode)
2.
二分法题目有2个细节点:
1.循环终止条件:是left<right ? 还是 left<=right?
2.选取middle时:if(nums[middke]>target): right = middle?还是right = middle - 1?
解决这两个问题的方法:明确循环区间,确定循环不变量。
1,[left,right]:区间定义左闭右闭。那当我们算法中使用时就要坚持左闭右闭的原则
设 left = 0; right = nums-1; while(left ? right)这里的“?”应该是"<"还是"<="?因为双闭区间代表着所有的元素都是待考验的,都有可能是等于target的值,所以应该把left=right的情况考虑进去。比如[0,0]这个数组只有一个数字,所以左右两边都是0,用<来判断就出错了。接着下一步while(left<= right){if(nums[middle]>target){right = middle?还是right = middle - 1? }} 显然是middle-1;理由是:刚才我们探讨了循环区间中下标的意义:因为双闭区间代表着所有的元素都是待考验的。所以middle已经排除,就不要了,所以right = middle。
public class 二分法 {
//左闭右闭
public static void main(String[] args){
int[] nums = new int[]{1,3,4,7,9,11,15,16,47,57};
int target = 3;
int ans = erfen(nums,target);
if(ans ==-1){
System.out.println("没有目标值"+target);
}else {
System.out.println("位置:"+ans+"查找到"+nums[ans]);
}
}
public static int erfen(int[] nums,int target){
int left =0;
int right = nums.length-1;
int middle = 0;
while (left <= right) {
middle = (left+right)/2;
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
/*System.out.println("位置:"+middle+"查找到"+nums[middle]);
break;*/
return middle;
}
}
return -1;
}
}
2.[left,right)左闭右开:根据上面的推断,由于最右侧的数据不在考虑范围内,所以当left=right的时候就代表结束了。也可以这么看[1,1)这个数组,这是非法的,因为不能左边取1右边不取1。所以应该是left<right。第二点:while(left<= right){if(nums[middle]>target){right = middle?还是right = middle - 1? }} 因为右边不包含在考虑范围内,若是moddle-1那么就会出错。middle已经确定不是target那么就让middle做新边界。但是left更新的时候要注意是middle+1,和上面一样。
public class 二分法 {
//左闭右闭
public static void main(String[] args){
int[] nums = new int[]{1,3,4,7,9,11,15,16,47,57};
int target = 3;
int ans = erfen1(nums,target);
/*if(ans ==-1){
System.out.println("没有目标值"+target);
}else {
System.out.println("位置:"+ans+"查找到"+nums[ans]);
}*/
int ans2 = erfen2(nums,target);
if(ans ==-1){
System.out.println("没有目标值"+target);
}else {
System.out.println("位置:"+ans2+"查找到"+nums[ans2]);
}
}
public static int erfen1(int[] nums,int target){
int left =0;
int right = nums.length-1;
int middle = 0;
while (left <= right) {
middle = (left+right)/2;
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
/*System.out.println("位置:"+middle+"查找到"+nums[middle]);
break;*/
return middle;
}
}
return -1;
}
public static int erfen2(int[] nums,int target){
int left =0;
int right = nums.length-1;
int middle = 0;
while (left < right) {
middle = (left+right)/2;
if (nums[middle] > target) {
right = middle ;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
/*System.out.println("位置:"+middle+"查找到"+nums[middle]);
break;*/
return middle;
}
}
return -1;
}
}
总结:二分法本身并不复杂,它针对有序查找target任务很好用,时间复杂度是O(logN),要注意的是,middle更新的时候为(left+right)/2,这里left和right相加要防止int溢出。还有就是循环边界的考量,得清楚自己的边界设置。
Leetcode35题:主要与二分法区别就是,最后return -1换成如下代码:
if(nums[middle]>target){
return middle;
}else{
return middle+1;
}
首先二分法用的是左闭右闭,我发现每次循环结束,middle指的位置一定是target的附近(在这个数组中最接近target的数)。所以只需要判定nums[middle]和target的大小,小于middle位置就在middle否则middle+1;
Leetcode27
移除元素(双指针思想)
1.什么时候使用库函数?
当库函数是你大问题的一小步的时候,使用它;
关于移除元素:暴力的方法显然是:寻找到删除的数字位置,从该个数字后开始依次覆盖。时间复杂度是O(n2)
我们思考一下如何优化?首先删除一个元素找到他是一定的O(n)但是删除它的时候,不一定必须按顺序覆盖:快慢指针可以减少这一次循环 。快慢指针实现代码:思路是1.首先满指针和快指针都等于0;设置一个赋值操作语句(我们期望这块语句可以将不是val的值的快指针覆盖慢指针值),0位置若不是val则原地赋值,若是val,那就不走赋值语句,同时也就不更新slow。
public static int 双指针(int[] nums,int target){
int slow =0;
int fast=0;
for(fast=0;fast<nums.length;fast++){
if(nums[fast]!=target){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}