【算法】LeetCode二分查找例题汇总(持续更新)

前言

刷LeetCode的时候,发现能实现二分查找的写法有很多,有很多帖子讨论不同的边界问题会出现不同的问题:

  • 死循环问题
  • 仅一部分的测试用例可以通过

为了避免以上问题,收敛下题目,记住解题思路。

2. 例题

2.1 最简单的二分查找算法题

LeetCode: 704. 二分查找

   public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
		// [left,left] 和 [right,right] 都代表区间只有一个元素,一旦出现了[right,left] 代表程序尽最大努力找target,依旧还是找不到
        while (left <= right) {
            int mid = (right - left) / 2 + left;
            if (target == nums[mid]) {
                return mid;
            } else if (target < nums[mid]) { 
                // 目标在 mid 的左边 移动右指针, 舍弃右边元素
                right = mid - 1;
            } else if (target > nums[mid]) {
                // 同理,移动左指针
                left = mid + 1;
            }
        }

        return -1;
    }

2.2 第一个错误的版本

LeetCode: 278. 第一个错误的版本

public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        int mid = 0;
        while (right > left) {
            // 向下取整 
            // [A] left = 1, right = 1, (1-1) + 1 = 1
            // [A, B, C] left = 1, right = 3 , (3-1) / 2 + 1 = 2
            // [A, B, C, D] left = 1, right = 4 , (4-1) / 2 + 1 = 2
            mid = (right - left) / 2 + left;
            if (isBadVersion(mid)) {
                // 答案落在 [left, mid] 中
                right = mid;
            } else {
                // 答案落在 [mid+1, right] 中
                left = mid + 1;
            }
        }

        // right == left 指针相遇, 则待考察元素只剩下一个。由题意得, 序列中必然出现错误版本, 则该元素即为所求。
        return left;
    }
2.2.1 理解为什么是 left = mid + 1, 而 right = mid
当序列中有三个元素, (下标从0开始)

  left == 0, right == 2
  [true, false, false] 
  mid == 0 + (0 + 2) / 2 = 1
  往 mid 的左边找可以找到答案
   left == 0, right == 1  (为什么不是 mid - 1 而是 mid) 因为当前Mid可能是答案,不要漏掉了。
     
   [true, true, false]
   往 mid 的右边找可以找到答案
   left == 1, right == 2  (为什么可以是 mid + 1) 因为当前Mid不可能是答案 (答案是要找false节点,而不是true节点)

2.3 有序数组的平方

LeetCode:977. 有序数组的平方

    public int[] sortedSquares(int[] nums) {

        // 越小的值,平方后会变得越大
        int left = 0;
        int right = nums.length - 1;

        // 双指针, 加入结果集的指针, 继续移动
        int[] ans = new int[nums.length];

        // ans 期望是从小到大排列, 从后往前一次填充候选者
        for (int i = ans.length - 1; i >= 0; i--) {
            // 对比两个指针元素
            int leftValue = nums[left] * nums[left];
            int rightValue = nums[right] * nums[right];

            if (leftValue > rightValue ) {
                ans[i] = leftValue;
                left++;
            } else {
                ans[i] = rightValue;
                right--;
            }
        }

        return ans;
    }
2.3.1 理解边界条件

笔者写的答案是用 ans 的数组大小作为边界条件。LeetCode 官方题解写的是 i <= j 为边界,现在理解下这种方式的定义。
[1, 2, 3] 序列中 left = 0, right = 2, 选举后3加入ans,序列变成 [1,2]
[1, 2] 序列中 left = 0, right = 1, 选举后2加入ans, 序列变成[1]
[1] 序列中, left = 0, right = 0, 选举后1加入ans,序列变成[]
程序结束

结论是,两指针相遇后,还需要把最后一个元素加入序列,所以边界取等号

2.4 搜索插入位置

LeetCode: 35. 搜索插入位置

    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int mid;
        while (left  <= right ) {
            mid = left  + (right - left ) / 2;
            // nums中的元素不重复,如果 target 跟 nums 中的X一样,则X所在的下标即为待插入位置
            // 如序列 1 3 4 5; 插入 3  
            if (target == nums[mid]) {
                return mid;
            }  else if (target < nums[mid]) { 
                // 待插入的元素应该在 mid 的左边 移动右指针, 舍弃右边元素
                right = mid - 1;
            } else if (target > nums[mid]) {
                // 同理,移动左指针
                left = mid + 1;
            }
        }
        // 二分没有找到插入的位置, 那么target 一定小于 left, 从left 位置插入既能满足题解。
        return left;
    }
  • 摘一段解析 为什么最后可以返回 left
    在这里插入图片描述

3.1 待考察元素的区间要覆盖全

2.1 和 2.2 中二分查找法,区间都是是 [left......mid-1] [mid,mid] [mid+1......right],其中mid为待考察元素。2.2 中left可以作为结果返回,是根据题目的语义决定的。

你可能感兴趣的:(Java,基础,算法,算法,leetcode,数据结构)