二分查找算法(折半查找)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码:

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) {
            // bug : left + right 会溢出
            // 整型的最大值是 (2^31) - 1 = 2147483647
            int mid = left + (right - left) / 2;
            // int mid = (left + right) >>> 1;
            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]
            }
        }

        // left > right :没有元素

        return false;
    }

    // 时间复杂度:O(logn)
    // 空间复杂度:O(logn)
    // 递归写法
    public boolean containsR(int[] nums, int target) {
        if (nums == null || nums.length == 0) return false;
        return contains(nums, 0, nums.length - 1, target);
    }

    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 (target < nums[mid]) {
            return contains(nums, left, mid - 1, target);
        } else {
            return contains(nums, mid + 1, right, target);
        }
    }
}

这里会有一个bug
中间索引溢出的问题
在这里插入图片描述
解决方法
在这里插入图片描述
第二种,无符号右移一位
在这里插入图片描述

2. 变型的二分查找

2.1 查找第一个等于目标值的下标

在这里插入图片描述
代码

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]) {
                // 符合下面的两个条件之一就返回 mid :
                // 1. mid 是数组的第一个元素
                // 2. mid 前面的那个元素不等于 target
                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;
    }

2.2 查找第一个大于等于目标值下标的代码

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]) {
                // 符合下面的两个条件之一就返回 mid :
                // 1. mid 是数组的第一个元素
                // 2. mid 前面的那个元素小于 target
                if (mid == 0 || nums[mid - 1] < target) return mid;
                else right = mid - 1;
            } else { // target > nums[mid]
                left = mid + 1;
            }
        }
        return -1;
    }

2.3 查找最后一个等于目标值的下标

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]) {
                // 符合下面的两个条件之一就返回 mid :
                // 1. mid 是数组的最后一个元素
                // 2. 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;
    }

2.4 查找最后一个小于等于目标值的下标

代码

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;
    }

3. 二分查找应用

3.1 如何定位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"));
    }
}

4.二分查找的两种思路

4.1 不断在循环体中查找目标元素

在这里插入图片描述

4.2 再循环体中排除一定不存在的目标元素区间

在这里插入图片描述
在这里插入图片描述

public int search2(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
        // 需要后处理,因为在循环中,还有一个元素没有处理
        if (nums[left] == target) return left;
        return -1;
    }

思路二第二种实现方式
在这里插入图片描述

public int search3(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
        // 需要后处理,因为在循环中,还有一个元素没有处理
        if (nums[left] == target) return left;
        return -1;
    }

在这里插入图片描述
代码

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[2];
        int fIndex = getfirstIndex(nums,target);
        int lIndex = getlastIndex(nums,target);
        result[0] = fIndex;
        result[1] = lIndex;
        return result;
    }

    private int getfirstIndex(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){
                // 需要往前看一下
                //这块出错了
                if((mid==0) || nums[mid-1] < target){
                    //这块出错了
                    return mid;
                }else{
                    right = mid-1;
                }
            }else if(nums[mid] < target){
                left = mid +1;
            }else{
                right = mid -1;
            }
        } 
        return -1;
    }
    private int getlastIndex(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){
                // 需要往后看一下
                if((mid == nums.length -1) || nums[mid+1] > target){
                    return mid;
                }else{
                    left = mid+1;
                }
            }else if(nums[mid] < target){
                left = mid +1;
            }else{
                right = mid -1;
            }
        } 
        return -1;
    }
}

在这里插入图片描述
在这里插入图片描述

public int[] searchRange(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return new int[]{-1, -1};

        int firstTargetIndex = searchFirstTargetIndex(nums, target);
        if (firstTargetIndex == -1) {
            return new int[]{-1, -1};
        }
        int lastTargetIndex = searchLastTargetIndex(nums, target);
        return new int[]{firstTargetIndex, lastTargetIndex};
    }

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

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

在这里插入图片描述
思路:先画图穷举所有可能性,在找规律
在这里插入图片描述

public int searchInsert1(int[] nums, int target) {
        if (nums == null) return -1;
        if (nums.length == 0) return 0;
        //如果target都大于最后一个元素了,那么就将他放到最后一个元素上
        if (target > nums[nums.length - 1]) return nums.length;
        // 二分查找第一个大于等于 target 的索引
        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 {
                left = mid + 1;
            }
        }
        return -1;
    }

思路二
在这里插入图片描述
在这里插入图片描述

public int searchInsert(int[] nums, int target) {
        if (nums == null) return -1;
        if (nums.length == 0) return 0;
        // 二分查找第一个大于等于 target 的索引
        int left = 0;
        int right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (target > nums[mid]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值