算法二分查找—Java版

image.png

知识融汇

  • 实际上二分法也是双指针算法,属于相向双指针,使用left指向初始节点,right指向最后节点。

使用条件

  • 数组有序(30%-40%的概率是二分)

  • 复杂度比O(n)小,大概为O(logn)的复杂度

  • 可以在数组中找到一个分割位置,使得一半满足条件继续搜索,另一半不满足条件。

  • 找一个最大值/最小值使得某个条件被满足。

Note:此二分法模板使用的是双指针实现,如果一个题目有序,也可以使用此模板的双指针法进行求解。

算法万能模板

非递归模板

模板重点记住两点,万能while中条件的判断,和while循环外单独判断。一般建议使用非递归模板,逻辑清晰容易发现错误,递归不太直观。

public int binarySearch_f(int[] nums, int target) {
    
    
    if (nums.length == 0 || nums == null) {
    
    
        return -1;
    }
    int start = 0;
    int end = nums.length - 1;
    //重点:start+1<end ,此时会少判断一次需要单独拿出来判断,
    //避免边界条件出现死循环,这也是此模板的灵魂,精髓
    while (start+1<end){
    
    
        int mid = start + (end-start)/2;
        if(nums[mid]==target){
    
    
            return mid;
        } else if (nums[mid]>target) {
    
    
            end = mid;  // 不用管要不要mid-1 或者 mid+1 ,全部写成 mid
        } else{
    
    
            start = mid;
        }
    }
    // 重点:精髓,因为循环中跳过了中间相邻两个元素的判断,因此需要在外面单独判断
    // 此时经过while循环,start 与 end 相邻,即 start+1==end
    if(nums[start]==target){
    
    
        return start;
    }
    if(nums[end]==target){
    
    
        return end;
    }
    return -1;
}
递归模板
    public int binarySearch_f2(int[] nums,int start , int end , int target) {
    
    
        if (nums.length == 0 || nums == null) {
    
    
            return -1;
        }
        int middle = start + (end-start)/2;
        //边界条件的判断
        if(start+1==end){
    
    
            if(nums[start]==target){
    
    
                return start;
            }else if(nums[end]==target){
    
    
                return end;
            }else {
    
    
                return -1;
            }
        }
        if(nums[middle]==target){
    
    
            return middle;
        }
        if(nums[middle]<target){
    
    
            return binarySearch_f2(nums,middle,end,target);
        }else {
    
    
            return binarySearch_f2(nums,start,middle,target);
        }
    }

LeetCode练习

  • 在排序数组中查找元素的第一个和最后一个位置[力扣]

参考图1,请写代码实现二分查找,分别查数组中第一次target出现的下标和最后一次出现的下标,当解决这两个问题,其他问题只需要在此基础上简单变换即可。

  • 第一次出现的位置

    public int binarySearch_first(int[] nums, int target) {
          
          
            if (nums.length == 0 || nums == null) {
          
          
                return -1;
            }
            int start = 0;
            int end = nums.length - 1;
    
            while (start+1<end){
          
          
                int mid = start + (end-start)/2;
                if(nums[mid]==target){
          
          
                    end = mid;   // 当找到了一个并不能直接返回,而是将其作为右边界,包含关系
                } else if (nums[mid]>target) {
          
          
                    end = mid;   // 不用管要不要mid-1 或者 mid+1 ,全部写成 mid
                } else{
          
          
                    start = mid;
                }
            }
    
            // 注意 到底是两个if还是else if
            if(nums[start]==target){
          
          
                first = start;
            }else if(nums[end]==target){
          
          
                first = end;
            }
            return first;
    
  • 最后一次出现的位置

    public int binarySearch_last(int[] nums, int target) {
          
          
            if (nums.length == 0 || nums == null) {
          
          
                return -1;
            }
            int start = 0;
            int end = nums.length - 1;
    
            while (start+1<end){
          
          
                int mid = start + (end-start)/2;
                if(nums[mid]==target){
          
          
                    start = mid;   // 当找到了一个并不能直接返回,而是将其作为左边界,包含关系
                } else if (nums[mid]>target) {
          
          
                    end = mid;   // 不用管要不要mid-1 或者 mid+1 ,全部写成 mid
                } else{
          
          
                    start = mid;
                }
            }
    
            //注意这两个边界点的判断顺序,return 可以两个if
            if(nums[end]==target){
          
          
                return end;
            }
            if(nums[start]==target){
          
          
                return start;
            }
            return -1;
        }
    
    1. 搜索旋转排序数组[力扣]
    public int search(int[] nums, int target) {
          
          
            int left = 0;
            int right = nums.length-1;
    
            while(left+1<right){
          
          
                int middle = left + (right-left)/2;
                if(nums[middle]>nums[left]){
          
           //左侧有序
                    if(target>=nums[left] && target<=nums[middle]){
          
          
                        right = middle;
                    }else{
          
          
                        left = middle;
                    }
                }
    
                if(nums[middle]<nums[right]){
          
           //右侧有序
                    if(target>=nums[middle] && target<=nums[right]){
          
          
                        left = middle;
                    }else{
          
          
                        right = middle;
                    }
                }
            }
    
            if(nums[left]==target){
          
          
                return left;
            }
    
            if(nums[right]==target){
          
          
                return right;
            }
            return -1;
        }
    
    1. 寻找峰值[力扣]
    public int findPeakElement(int[] nums) {
          
          
            //此题有两点第一边界是负无穷,因此,只要某一边在爬坡就一定会有山峰,因为就算一致上升,那么到了边界就是悬崖,即最后一个元素是山峰
            //因此,这道题的二分技巧是丢弃下降的一半,寻找上升的一半
            int left = 0;
            int right = nums.length;
    
            while (left+1<right){
          
          
                int middle = left+(right-left)/2;
                if(nums[middle]<nums[middle+1]){
          
          
                    left = middle;
                }else{
          
          
                    right = middle;
                }
            }
    
            if(nums[left]>nums[right]){
          
          
                return left;
            }else {
          
          
                return right;
            }
        }
    
    1. 寻找旋转排序数组中的最小值[力扣]
        public int findMin(int[] nums) {
          
          
            int left=0;
            int right = nums.length-1;
    
            if(nums[left]<nums[right]){
          
          
                //如果数组有序 
                return nums[left];
            }
    
            while(left+1<right){
          
          
                int mid = left + (right-left)/2;
                if(nums[mid]>nums[left]){
          
          
                    //左边有序,往右边找
                    left = mid;
    
                } else if (nums[mid] < nums[right]) {
          
          
                    //右边有序,往左边找
                    right = mid;
                }
            }
    
            return Math.min(nums[left],nums[right]);
        }
    
    1. 有效三角形的个数[力扣]
    public int last_target(int[] nums ,int start, int end , int target){
          
          
            //从nums的start到end中寻找最后一个满足target的下标
            while(start+1<end){
          
          
                int mid = (start+end)/2;
                if(nums[mid]<target){
          
          
                    start = mid;
                }else {
          
          
                    end = mid;
                }
            }
            if(end<nums.length && nums[end]<target){
          
          
                return end;
            }
            if(start<nums.length && nums[start]<target){
          
          
                return start;
            }
            return -1;
        }
    
    public int triangleNumber(int[] nums) {
          
          
        //解法:两短边之和大于第三边,先排序,再查找
        Arrays.sort(nums);
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
          
          
            for (int j = i+1; j <nums.length ; j++) {
          
          
                // i j 指向的是两个短边,从后面的数中搜索第一个满足条件的下标
                int fdx = last_target(nums,j+1,nums.length-1,nums[i]+nums[j]);
                if(fdx!=-1){
          
          
                    count += (fdx-j);
                }
            }
        }
        return count;
    }
    

猜你喜欢

转载自blog.csdn.net/cj151525/article/details/129022034