二分查找

1. 二分搜索模板

1.1. 基本二分搜索

  • leetcode 35
  • leetcode 74
  • leetcode 374

如:int[ ] nums = {1,3,4,5,6,8,12,14,16},target = 8 ,在nums中找到target的索引,找不到返回-1。

public int binarySearch(int[] nums, int target){
    int left = 0;
    int right = nums.length - 1;
    while(left <= right){
        int mid = left + (right-left)/2;
        if(nums[mid] == target){
            return mid;
        } else if(nums[mid] > target){
            right = mid - 1;
        } else if(nums[mid] < target){
            left = mid +1;
        }
    }
    return -1
}

说明
line 4:left大于right为结束条件
line 5:不写成 int mid = (right+left)/2是为了防止int类型溢出
line14:while循环中未找到则返回-1

1.2. 查找最左边界

  • leetcode 34
  • leetcode 275
  • leetcode 278

如:int[ ] nums = {1,3,4,8,8,8,12,14,16},target = 8 ,在nums中找到target第一次出现的索引,找不到返回-1。

public int leftBoundary(int[] nums, int target){
    int left = 0;
    int right = nums.length - 1;
    while(left <= right){
        int mid = left + (right-left)/2;
        if(nums[mid] == target){
            right = mid - 1;
        } else if(nums[mid] > target){
            right = mid - 1;
        } else if(nums[mid] < target){
            left = mid +1;
        }
    }
    if(left>=nums.length || nums[left]!=target){
        return -1;
    }
    return left;
}

说明
line 4:left大于right为结束条件
line 5:不写成 int mid = (right+left)/2是为了防止int类型溢出
line7:因为要找最左边界,所以向左移动区间
line14:因为返回left,所以要防止left越界,同时要保证left处的值是target

1.3. 查找最右边界

  • leetcode 34

如:int[ ] nums = {1,3,4,8,8,8,12,14,16},target = 8 ,在nums中找到target最后一次出现的索引,找不到返回-1。

public int rightBoundary(int[] nums, int target){
    int left = 0;
    int right = nums.length - 1;
    while(left <= right){
        int mid = left + (right-left)/2;
        if(nums[mid] == target){
            left = mid +1;
        } else if(nums[mid] > target){
            right = mid - 1;
        } else if(nums[mid] < target){
            left = mid +1;
        }
    }
    if(right<0 || nums[right]!=target){
        return -1;
    }
    return right;
}

说明
line 4:left大于right为结束条件
line 5:不写成 int mid = (right+left)/2是为了防止int类型溢出
line7:因为要找最右边界,所以向右移动区间
line14:因为返回right,所以要防止right越界,同时保证right处的值是target

2. 二分搜索变种

2.1. 旋转数组

此类题数组不完全有序,有的甚至要考虑重复值,此类题利用数组的部分有序性

  • leetcode 33:旋转数组,找某个数,元素不重复
  • leetcode 81:旋转数组,找某个数,元素重复
  • leetcode 153:旋转数组,找最小值,元素不重复
  • leetcode 154:旋转数组,找最小值,元素重复

2.1.1 leetcode 33

public int search(int[] nums, int target) {
    int n = nums.length;
    int left = 0;
    int right = n-1;
    while(left<=right){
        int mid = left +(right-left)/2;
        if(nums[mid]==target){
            return mid;
        }
        if(nums[left]<=nums[mid]){
            if(nums[left]<=target && target<nums[mid]){
                right = mid-1;
            }else{
                left = mid+1;
            }
        }else{
            if(nums[mid]<target && target<=nums[right]){
                left = mid+1;
            }else{
                right = mid-1;
            }
        }
    }
    return -1;
}

说明

  • 若nums[mid]==target,直接返回
  • 若nums[left]<=numsmid,说明左侧区间[left,mid)连续递增,此时:
    • 若nums[left]<=target<nums[mid],则在左侧区间查找,令right=mid-1;
    • 否则,则在右侧区间查找,令left=mid+1。
  • 否则说明右侧区间(mid,right]连续递增,此时:
    • 若nums[mid]<target<=nums[right],则在右侧区间查找,令left=mid+1;
    • 否则,在左侧区间查找,令right=mid-1。

2.1.2 leetcode 81

public boolean search(int[] nums, int target) {
    int n = nums.length;
    int left = 0;
    int right = n-1;
    while(left<=right){
        int mid = left +(right-left)/2;
        if(nums[mid]==target){
            return true;
        }
        if(nums[left]<nums[mid]){
            if(nums[left]<=target && target<nums[mid]){
                right = mid-1;
            }else{
                left = mid+1;
            }
        }else if(nums[left]==nums[mid]){
            left++;
        }else{
            if(nums[mid]<target && target<=nums[right]){
                left = mid+1;
            }else{
                right = mid-1;
            }
        }
    }
    return false;
}

说明

  • 若nums[mid]==target,直接返回
  • 存在重复元素,所以与leetcode33的区别是,当nums[left]==nums[mid]时,无法判断区间的单调性,这个时候通过left++去除重复元素即可。与leetcode33相比只需要增加line16-line17即可。
  • 若nums[left]<nums[mid],说明左侧区间[left,mid)连续递增,此时:
    • 若nums[left]<=target<nums[mid],则在左侧区间查找,令right=mid-1;
    • 否则,则在右侧区间查找,令left=mid+1。
  • 否则说明右侧区间(mid,right]连续递增,此时:
    • 若nums[mid]<target<=nums[right],则在右侧区间查找,令left=mid+1;
    • 否则,在左侧区间查找,令right=mid-1。

2.1.3 leetcode 153

public int findMin(int[] nums) {
    int n = nums.length;
    int left = 0;
    int right = n-1;
    while(left<right){
        int mid = left +(right-left)/2;
        if(nums[mid]<nums[right]){
            right = mid;
        }else{
            left = mid+1;
        }
    }
    return nums[left];
}

说明

  • 假设数组最后一个元素为x,以最小值为分界,最小值右边(包含最小值)一定小于x,左边一定大于x。
  • 若nums[mid]<nums[right],说明最小值在[left,mid],此时right=mid
  • 否则最小值在[mid+1,right]中,此时left=mid+1。
  • 找最小值相当于最左边界,需要返回left,通过测试几个例子while中条件需要为left<right。

2.1.4 leetcode 154

 public int findMin(int[] nums) {
    int n = nums.length;
    int left = 0;
    int right = n-1;
    while(left<right){
        int mid = left +(right-left)/2;
        if(nums[mid]<nums[right]){
            right = mid;
        }else if(nums[mid]==nums[right]){
            right--;
        }else{
            left = mid+1;
        }
    }
    return nums[left];
}

说明

  • 假设数组最后一个元素为x,以最小值为分界,最小值右边(包含最小值)一定小于等于x,左边一定大于等于x。
  • 若nums[mid]<nums[right],说明最小值在[left,mid],此时right=mid
  • 若nums[mid]==nums[right],最小值可能在左边也可能在右边,所以需要消除重复值,right–
  • 否则最小值在[mid+1,right]中,此时left=mid+1。
  • 找最小值相当于最左边界,需要返回left,通过测试几个例子while中条件需要为left<right。

2.2 找峰值

  • leetcode 162

如:峰值元素是指其值严格大于左右相邻值的元素。给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值 所在位置即可。可以假设 nums[-1] = nums[n] = -∞ 。

public int findPeakElement(int[] nums) {
    int left = 0;
    int right = nums.length-1;
    while(left<=right){
        int mid = left + (right-left)/2;
        if(mid == nums.length-1){
            return mid;
        }
        if(nums[mid]> nums[mid+1]){
            right = mid-1;
        } else if(nums[mid]<= nums[mid+1]){
            left = mid+1;
        }
    }
    return left;
}

说明

  • 二分的mid处,可能是峰值、下坡、上坡
  • 是峰值,则找到了
  • 是上坡/下坡,则沿着上坡走(相当于爬山),一定能到峰值;但是若沿着下坡走则可能遇到新的峰值也可能一直下坡
  • 所以,问题的关键是“爬山”,只沿着上坡方向走

2.3 找重复数

  • leetcode 287

如:给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 额外空间。

class Solution {
    private int[] nums;
    public int findDuplicate(int[] nums) {
        this.nums = nums;
        int left= 1;
        int right = n;
        while(left<=right){
            int mid = left +(right-left)/2;
            if(countNum(mid)>mid){
                right = mid-1;
            }else{
                left = mid+1;
            }
        }
        if(left>n||countNum(left)<=left){
            return -1;
        }
        return left;
    }

    public int countNum(int n){
        int res = 0;
        for(int a:nums){
            if(a<=n){
                res++;
            }
        }
        return res;
    }
}

说明

  • 用时间换空间
  • 二分搜索从1到n的数组,对于每个数a在nums中查找有count个数<=这个数。若count>a,则重复数在1到a之间,否则在a到n之间

2.4 平方数

  • leetcode 69
  • leetcode 367

如:给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。不能使用任何内置的库函数,如 sqrt 。

public boolean isPerfectSquare(int num) {
    long left=0;
    long right = num;
    while(left<=right){
        long mid = left +(right-left)/2;
        if(mid*mid==num){
            return true;
        } else if(mid*mid<num){
            left = mid+1;
        } else if(mid*mid>num){
            right = mid-1;
        }
    }
    return false;
}

说明
line 2、3、5:防止mid*mid溢出,所以要用long类型

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值