LeetCode | 704 BinarySearch

https://github.com/dolphinmind/datastructure/tree/datastructure-array

思考

二分法在进行问题的拆解过程中,子问题与父问题性质相同,代码随想录-二分法引入开闭区间概念,底层核心是什么?

  • left = 0,0是索引的起始,在C系列语言,即左边界是闭合的;开闭与右边界索引选取的关联性最大;

  • 若right = length,在C系列语言中nums[length]是超出索引范围之外,取不到,即这种[left,right),左闭右开区间。在进行mid判断的时候,即把问题区间一分为二,如果选取的是mid的左侧区间,为了保持子问题和父问题边界的逻辑一致性,即左闭右开,那么right = nums[mid] 这个值我取不到,就符合条件;倘若是mid的右侧区间,那么左侧边界值就一定要取到,那么必须使用 left = nums[mid+1]

  • 若right = length - 1,则是同理,父问题区间类型为左闭右闭,那么子问题也要保持边界的逻辑一致性

通过上面的分析,借由开闭区间这个思路,就可以判断循环逻辑判断的是否取等

[left, right] while(left <= right)
[left, right) while(left < right)

主类

package com.github.dolphinmind.array.binarysearch;

/**
 * @author dolphinmind
 * @ClassName BinarySearch
 * @description 704 二分法搜索
 *              前提条件:有序数组,且无重复元素
 *              循环判断:专注索引
 *              逻辑判断:专注值
 *              特殊情况:无结果,返回-1
 *              技巧:mid = left + ((right - left) >> 1)
 * @date 2024/7/31
 */

public class BinarySearch {

    private int left;
    private int right;
    private int mid;

    /**
     * 二分法搜索:左闭右闭区间
     * 分析:循环条件看索引,中间索引采用移位,逻辑判断条件优先选取目标,循环不成立有兜底 -1
     * @param nums
     * @param target
     * @return
     */
    public int binarySearchRightClose(int[] nums, int target) {
        left = 0;
        right = nums.length - 1;

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (target == nums[mid]) {
                return mid;
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid;
            }
        }

        return -1;

    }

    /**
     * 二分法搜索:左闭右开区间
     * @param nums
     * @param target
     * @return
     */
    public int binarySearchRightOpen(int[] nums, int target) {
        left = 0;
        right = nums.length;

        while (left < right) {
            mid = left + ((right - left) >> 1);
            if (target == nums[mid]) {
                return mid;
            } else if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid - 1;
            }
        }

        return -1;

    }

    /**
     * <p>
     *     左右边界索引的二分查找,适用于查找左边界和右边界,但同样适应于数组中只存在一个target的情况
     *     假设target = nums[mid]一开始就成立
     *     尽管在第一次比较时,right或left可能看似"跳过了"目标值,
     *     但由于target唯一性,后续的搜索范围更新操作不会导致left或right错过target
     *     对于的就是左边界索引的left = mid + 1, 有边界索引的 right = mid - 1
     *     left 或 right都会正确地指向target的索引位置,成为有效的左边界或有边界索引
     * </p>
     */

    /**
     * 前置条件:有序数组,元素可允许可重复
     * Left Bound Index ,指的是目标值首次出现的位置,如果数组中有多个相同的元素(即重复值),左边界索引是目标值在数组中的最早出现位置
     * @param nums
     * @param target
     * @return
     */
    public int leftBound(int[] nums, int target) {
        if (nums.length == 0) {
            return -1;
        }

        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }

        int left = 0;
        int right = nums.length - 1;

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

        // 增加边界检查
        if (left >= nums.length || nums[left] != target) {
            return -1;
        }
        return left;
    }


    /**
     * 前置条件:有序数组,元素可允许可重复
     * Right Bound Index ,指的是目标值最后出现的位置,如果数组中有多个相同的元素(即重复值),右边界索引是目标值在数组中的最晚出现位置
     * @param nums
     * @param target
     * @return
     */
    public int rightBound(int[] nums, int target) {
        if (nums.length == 0) {
            return -1;
        }

        if (target < nums[0] || target > nums[nums.length - 1]) {
            return -1;
        }

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

        if (right < 0 || nums[right] != target) {
            return -1;
        }

        return right;
    }

}

package com.github.dolphinmind.array.binarusearch;

import com.github.dolphinmind.array.binarysearch.BinarySearch;
import org.junit.Test;

/**
 * @author dolphinmind
 * @ClassName ApiTest
 * @description
 * @date 2024/7/31
 */

public class BinarySearchTest {

    /**
     * 找房子编号,不要找房子里面的内容
     */
    @Test
    public void test_binarySearchRightClose() {

        int[] nums = {-1, 0, 3, 5, 9, 12};
        int target = 9;

        BinarySearch binarySearch = new BinarySearch();
        int result = binarySearch.binarySearchRightClose(nums, target);
        System.out.println("左闭右闭区间选择:" + result);
    }

    @Test
    public void test_binarySearchRightOpen() {
//        int[] nums = {-1,0,3,5,9,12};
        int[] nums = {};
        int target = 2;

        BinarySearch binarySearch = new BinarySearch();
        int result = binarySearch.binarySearchRightOpen(nums, target);
        System.out.println("左闭右开区间选择:" + result);
    }


    @Test
    public void test_leftBound() {
        int[] nums = {-1, 0, 3, 5, 9, 12};
        int target = 9;

        BinarySearch binarySearch = new BinarySearch();
        int result = binarySearch.leftBound(nums, target);
        System.out.println("左边界索引:" + result);
    }

    @Test
    public void test_rightBound() {
        int[] nums = {-1,0,3,5,9,12};
//        int[] nums = {};
        int target = 2;

        BinarySearch binarySearch = new BinarySearch();
        int result = binarySearch.rightBound(nums, target);
        System.out.println("右边界索引:" + result);
    }
}

posted @ 2024-07-31 21:29  Neking  阅读(36)  评论(0编辑  收藏  举报