算法模板(一)(相关话题:二分搜索)

目录

一、寻找一个数(基本的二分搜索) 

二、寻找左侧边界的二分搜索

写法一

写法二

相关题目

参考文章


一、寻找一个数(基本的二分搜索) 

算法模板(一)(相关话题:二分搜索)_第1张图片

int binary_search(int[] nums, int target) {
    int left = 0, 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) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}

1、为什么 while 循环的条件中是 <=,而不是 <

答:因为初始化right的赋值是nums.length - 1,即最后一个元素的索引,而不是nums.length

这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间[left, right],后者相当于左闭右开区间[left, right),因为索引大小为nums.length是越界的。

2、为什么left = mid + 1right = mid - 1?我看有的代码是right = mid或者left = mid,没有这些加加减减,到底怎么回事,怎么判断

答:这也是二分查找的一个难点,不过只要你能理解前面的内容,就能够很容易判断。

刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即[left, right]。那么当我们发现索引mid不是要找的target时,下一步应该去搜索哪里呢?

当然是去搜索[left, mid-1]或者[mid+1, right]对不对?因为mid已经搜索过,应该从搜索区间中去除

3、为什么返回left而不是right

答:都是一样的,因为 while 终止的条件是left == right

二、寻找左侧边界的二分搜索

比如我们给定数组1,2,3,4,4,4,5,6,7,7,8,9,我们需要查找第一个等于4的元素,「搜索区间」是[left, right]

写法一

因为我们初始化 right = nums.length-1,所以决定了我们的「搜索区间」是 [left, right],所以决定了 while (left <=right)。决定了下一步的二分区间为[left,mid-1] [mid+1,right]
同时也决定了 left = mid +1 和 right = mid-1

因为我们需找到 target 的最左侧索引,所以当 nums[mid] == target 时不要立即返回,而要令 right = mid - 1锁定左侧边界

int left_bound(int[] nums, int target) {
    int left = 0, 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) {
            // 别返回,锁定左侧边界
            right = mid - 1;
        }
    }
    // 最后要检查 left 越界的情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}

写法二

因为我们初始化 right = nums.length,所以决定了我们的「搜索区间」是 [left, right),所以决定了 while (left < right),决定了下一步的二分区间为[left,mid) [mid+1,right) ,同时也决定了right = mid 和  left = mid +1 

因为我们需找到 target 的最左侧索引,所以当 nums[mid] == target 时不要立即返回,而要令 right = mid 锁定左侧边界

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意

    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}

注意:第一次搜索到的nums[mid]==target的值可能是最左边,中间,最右边的数据。对于寻找左右边界的left和right的赋值的逻辑是不一样的。

相关题目

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

class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 分别寻找左边界和右边界
        int leftIndex = findBound(nums, target, true);
        int rightIndex = findBound(nums, target, false);

        // 如果左边界超过了右边界,说明没有找到目标值
        if (leftIndex > rightIndex) {
            return new int[]{-1, -1};
        }

        return new int[]{leftIndex, rightIndex};
    }

    private int findBound(int[] nums, int target, boolean findLeft) {
        int left = 0, right = nums.length - 1, ans = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                ans = mid; // 记录找到的位置
                // 如果寻找左边界,向左继续查找;如果寻找右边界,向右继续查找
                if (findLeft) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }
}

35. 搜索插入位置

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

        // 使用二分查找法
        while (left <= right) { // 应该是 left <= right,以涵盖所有情况
            int mid = left + (right - left) / 2;

            if (nums[mid] == target) {
                return mid; // 直接找到目标值,返回其索引
            } else if (nums[mid] < target) {
                left = mid + 1; // 调整左边界
            } else {
                right = mid - 1; // 调整右边界
            }
        }
        //如果目标值不存在于数组中,那么最终 left 指针将指向第一个大于目标值的元素位置,right 指针将指向最后一个小于目标值的元素位置。这是因为
        //如果目标值小于数组中的所有元素,循环结束时 left 为 0。
        //如果目标值大于数组中的所有元素,循环结束时 left 为 nums.length。
        //如果目标值位于数组中的两个元素之间,循环结束时 left 指向的是第一个大于目标值的元素位置。
        //因此,无论目标值是否存在于数组中,循环结束时 left 指针总是指向目标值应该被插入的位置
        return left;
    }
}

参考文章

算法刷题入门数据结构|二分查找 

你可能感兴趣的:(#,算法,算法,数据结构,排序算法)