LeetCode二分查找专题(刷题记录)


前言

二分查找的细节:

1.循环退出条件
2.计算中间索引溢出的bug
3.计算中间元素位置的时候是下取整
4.left和right的更新

二分查找的变形问题

1.查找第一个等于目标值的索引
2.查找第一个大于等于目标值的索引
3.查找最后一个等于目标值的索引
4.查找最后一个小于等于目标值的索引
5.应用:如何定位ip对应的城市
6.排除法的二分查找

实现的小结:

思路1.不断在循环体中查找目标元素
思路2.在循环体中不断的排除一定不存在目标元素的区间

二分查找数组未必有序的题型:

1.旋转数组问题
2.山脉数组问题


一、二分查找代码

1.在一个有序数组中查找目标元素target

/**
 * @author zzyuan
 * @create 2021-07-05 20:21
 */
public class BasicBinarySearch {

    //最基本的二分搜索代码
    //要求:数组是有序的
    //时间复杂度: o(logn)
    //空间复杂度: o(1)
    public boolean contains(int[] nums , int target){
        if(nums == null || nums.length == 0)
                return false;
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(target == nums[mid]){
                return true;
            }else if(target < nums[mid]){
                right = mid - 1;//下一次搜索区间[left ... mid-1]
            }else {//target > nums[mid]
                left = mid + 1;//下一次搜索区间[mid+1 ... right]
            }
        }
        return false;
    }


    //时间复杂度:o(logn)
    //空间复杂度: o(logn)
    //最基本的二分查找-递归写法
    private  boolean contains(int[] nums , int left , int right , int target){
        if(left > right)    return  false;
        int mid = left + (right - left) / 2;
        if(nums[mid] == target) return true;
        if(nums[mid] > target)  return contains(nums,left,mid-1,target);
        else  return contains(nums,mid+1,right,target);
    }

    
    //对降序的数组进行二分查找
    //改一下符号即可
    
}

2.查找第一个等于目标值target的下标

在包含重复元素数组中找到第一个等于target的下标

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

3.查找第一个大于等于目标值target的下标

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

4.查找最后一个等于目标值target的下标

public int lastTargetElement(int[] nums , int target){
        if(nums == null || nums.length == 0)    return  -1;
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(target == nums[mid]){
            	//1.mid是数组的最后一个元素或者mid后面那个元素不等于mid,我们就返回target
                if(mid == nums.length - 1 || nums[mid + 1] != target) return mid;
                else    left = mid + 1;
            }else if(target < nums[mid]){
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
        return -1;
    }

5.查找最后一个小于等于目标值target的下标

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

6.二分搜索应用案例:如何快速定位IP对应的城市

使用二分查找:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class IpLocationParser {
    private static class IpLocation {
        public long startIp;
        public long endIp;
        public String locationCity;
    }
    private static ArrayList<IpLocation> sortedIpLocations = new ArrayList<>();
    static {
        try {
            // 1. 读取文件,解析 ip 地址段
            BufferedReader reader =
                    new BufferedReader(new FileReader("data\\ip_location.txt"));
            String line = null;
            while ((line = reader.readLine()) != null) {
                String[] temps = line.split(" ");
                IpLocation ipLocation = new IpLocation();
                ipLocation.startIp = ip2Score(temps[0]);
                ipLocation.endIp = ip2Score(temps[1]);
                ipLocation.locationCity = temps[2];
                sortedIpLocations.add(ipLocation);
            }
        } catch (IOException e) {
            throw new RuntimeException("解析 ip 地址库出错" + e);
        }

        // 2. 按照起始 ip 进行升序排列
        // 时间复杂度:O(nlogn)
        Collections.sort(sortedIpLocations, new Comparator<IpLocation>() {
            @Override
            public int compare(IpLocation o1, IpLocation o2) {
                if (o1.startIp < o2.startIp) return -1;
                else if (o1.startIp > o2.startIp) return 1;
                else return 0;
            }
        });
    }

    // 将ip转成长整型
    public static Long ip2Score(String ip) {
        String[] temps = ip.split("\\.");
        Long score = 256 * 256 * 256 * Long.parseLong(temps[0])
                + 256 * 256 * Long.parseLong(temps[1])
                + 256 * Long.parseLong(temps[2])
                + Long.parseLong(temps[3]);
        return score;
    }

    // 二分查找指定 ip 对应的城市
    // 时间复杂度:O(logn)
    public static String getIpLocation(String ip) {
        long score = ip2Score(ip);
        // 3. 在 sortedIpLocations 中找到最后一个 startIp 小于等于 score 的这个 ip 段
        int left = 0;
        int right = sortedIpLocations.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (score >= sortedIpLocations.get(mid).startIp) {
                if (mid == sortedIpLocations.size() - 1
                        || sortedIpLocations.get(mid + 1).startIp > score) {
                    if (score <= sortedIpLocations.get(mid).endIp) {
                        return sortedIpLocations.get(mid).locationCity;
                    }
                } else {
                    left = mid + 1;
                }
            } else { // target < nums[mid]
                right = mid - 1;
            }
        }
        return null;
    }

    public static void main(String[] args) {

        System.out.println(getIpLocation("202.101.48.198"));
    }
}

二、LeetCode

1.704. 二分查找

704. 二分查找

二分的两种思路

1.不断在循环体中查找目标元素
2.在循环体中排除一定不存在目标元素的区间

思路1代码实现:

class Solution {
	//不断在循环体中查找目标元素
    public int search(int[] nums, int target) {
        int l = 0 ;
        int r = nums.length - 1;
        while(l <= r){
            int m = l + (r - l) / 2;
            if(nums[m] < target){
                l = m + 1;
            }else if(nums[m] > target){
                r = m - 1;
            }else{
                return m;
            }
        }
        return -1;
    }
}

分类法1:

1.target  <=  nums[mid]
2.target  >   nums[mid]

在这里插入图片描述
分类法1代码实现:

class Solution {
	//在循环体中排除一定不存在目标元素的区间
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return -1;
        int left = 0;
        int right = nums.length - 1;
        // 搜索区间是 [left...right] 中的每个元素
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (target > nums[mid])
                left = mid + 1;
            else
                right = mid;
        }
        // 循环结束后:left == right
        // 需要后处理,因为在循环中,还有一个元素没有处理
        return (nums[left] == target) ? left : -1;
    }
}

分类法2:

1.target  <    nums[mid]
2.target  >=   nums[mid]

在这里插入图片描述
分类法2代码实现:

class Solution {
	//在循环体中排除一定不存在目标元素的区间
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return -1;
        int left = 0;
        int right = nums.length - 1;
        // 搜索区间是 [left...right] 中的每个元素
        while (left < right) {
            int mid = left + (right - left + 1) / 2;
            if (target < nums[mid])
                right = mid - 1;
            else
                left = mid;
        }
        // 循环结束后:left == right
        // 需要后处理,因为在循环中,还有一个元素没有处理
        return (nums[left] == target) ? left : -1;
    }
}

2.34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置

代码如下:

class Solution {
    //在循环体中排除一定不存在目标元素的区间
    public int[] searchRange(int[] nums, int target) {
        if(nums == null || nums.length == 0)    
            return new int[]{-1,-1};
   
        int l = 0;
        int r = nums.length - 1;
        int start = 0;
        int end = 0;
        while(l < r){
            int mid = l + r >> 1;
            if(nums[mid] >= target) r = mid;
            else    l = mid + 1;
        }
        if(nums[l] != target)    return new int[]{-1,-1};

        start = l;

        l = 0 ;
        r = nums.length - 1;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(nums[mid] <= target)  l = mid;
            else    r = mid - 1;
        }
        end = l;
        return new int[]{start,end};

    }
}

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值