二分查找刷题套路

理论

  • 每次划分完,一定是要缩小解空间的(这样就不会造成死循环)
  • 需要注意:循环条件的写法 (start <= end, start + 1 < end)
  • 取中间元素的写法: 统一: mid = start + ((end - start) >> 1);
    • java中可以这么写: mid = (start + end) >>> 1;
    • 为了可读性好: mid = start + (end - start)/2;

模板

模板1: low <= high

这种循环条件,保证二分的数组至少有一个元素。

int ans = -1;
while (low <= high) {
    int mid = low + (high - low) / 2;
    if (target <= A[mid]) {  // find first match
        ans = mid;
        high = mid - 1;
    } else {
        low = mid + 1;
    }
}
return ans;

模板2: low + 1 < high

始终保证二分时,至少有三个元素,这样不管怎么缩减空间,都不会造成死循环。

while (low + 1 < high) {
   int mid = low + (high - low) / 2;
   if (A[mid] < A[mid+1]) {
       low = mid;
   } else {
       high = mid;
   }
}

模板3: low < high

至少有两个元素才进行二分,结束条件就是只有一个元素了 low==high,或者 low > high

// 一定需要注意缩短空间的写法:(原因: [1,2] 取中点,倾向于左边,所以得到1)
while (low < high) {
    int mid = low + (high - low) / 2;
    if (xxx) { // remove left
        low = mid + 1;
    } else { // remove right part
        high = mid;
    }
}

模板的应用

  • ① 在有序数组中,精确查找某个数,返回下标,不存在返回-1 【写法1】
  • ② 在有序数组中,寻找第一个满足条件的位置,不存在返回-1 【写法1】
    • 例如:在有序数组中,找到第一个 >= target 的元素位置 (例如:查找插入位置)
    • 查找第一个等于target的元素位置
  • ③ 在有序数组中,寻找最后一个满足条件的位置,不存在返回-1 【写法1】
    • 在有序数组中,找到最后一个 < target 的元素位置
    • 寻找最后一个 = target的位置
  • ④ 在有序数组中,寻找最接近target的元素位置 【写法2】
    • e.g. find peak

练习题

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

class Solution { // time: O(log n), space: O(1)
    int searchFirst(int[] nums, int target) {
        int left = 0, right = nums.length - 1, ans = -1;        
        while (left <= right) {
            int mid = (left + right) >>> 1;
            if (nums[mid] == target) {
                ans = mid;
                right = mid - 1; // 重点是这句,相等之后,仍然往左继续找
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }

    int searchLast(int[] nums, int target) {
        int left = 0, right = nums.length - 1, ans = -1;        
        while (left <= right) {
            int mid = (left + right) >>> 1;
            if (nums[mid] == target) {
                ans = mid;
                left = mid + 1; // 重点是这句,相等之后,仍然往右继续找
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }

    public int[] searchRange(int[] nums, int target) {
        int left = searchFirst(nums, target);
        int right = searchLast(nums, target);
        return new int[] {left, right};
    }
}

35. 搜索插入位置 - [好题] 写法1更好

需要注意下ans的初始化。不过跟上面找最后一个元素是一样的。

class Solution { // time: O(log n), space: O(1)
    public int searchInsert(int[] nums, int target) {
        if (nums == null) {
            return -1;
        }
        int start = 0, end = nums.length - 1;
        // 找到最后一个比 target 小的元素位置: ans
        int ans = -1;
        while (start <= end) {
            int mid = start + (end - start) / 2;
            if (nums[mid] < target) { // 找到一个满足条件的,不要停。继续move
                ans = mid;
                start = mid + 1;
            } else {
                end = mid - 1;
            }
        }
        return ans + 1;
    }
}

28 · 搜索二维矩阵 - LintCode

这个题目,只能先按照第1列二分,找最后一个 <= target的位置,然后再在这一行进行二分。

     public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int m = matrix.length, n = matrix[0].length;
        // find last index <= target in first column
        int low = 0, high = m - 1;
        int ans = -1;
        while (low <= high) {
            int mid = low + (high - low) / 2;
            if (matrix[mid][0] == target) {
                return true;
            } else if (matrix[mid][0] < target) {
                ans = mid;
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        if (ans == -1) {
            return false;
        }

        low = 0;
        high = n - 1;
        while (low <= high) {
            int mid = low + (high - low) / 2;
            if (matrix[ans][mid] == target) {
                return true;
            } else if (matrix[ans][mid] < target) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return false;
    }

38 · 搜索二维矩阵(二) - LintCode

思路:这个题就是从右上角或者左下角开始,一次排除一行或一列。简单。思维有点急转弯。

74 · 第一个错误的代码版本 - LintCode

    int firstBadVersion(int n) {
        int low = 1, high = n;
        int ans = 1;
        // find first bad version in range [1, n] inclusive, answer must be exist
        while (low <= high) {
            int mid = low + (high - low) / 2;
            if (isBadVersion(mid)) {
                ans = mid;
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return ans;
    }

162. 寻找峰值 - 非常好,写法2

思路:二分最后还剩下2个元素,只要有三个就继续二分。比较条件,就是判断mid 和 mid + 1的大小。

   public int findPeakElement(int[] nums) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int low = 0, high = nums.length - 1;
        while (low + 1 < high) { // at least 3 elements between [low, high]
            int mid = low + (high - low) / 2;
            if (nums[mid] < nums[mid + 1]) {
                low = mid;
            } else {
                high = mid;
            }
        }
        return nums[low] > nums[high] ? low : high;
    }

248 · Count of Smaller Number - LintCode

思路:很好的题目。从有序数组中找第一个大于等于target的元素。需要注意ans的初始值。

    public List<Integer> countOfSmallerNumber(int[] a, int[] queries) {
        List<Integer> ans = new ArrayList<>();
        if (a == null || a.length == 0) {
            for (int x : queries) {
                ans.add(0);
            }
            return ans;
        }
        Arrays.sort(a);
        for(int x : queries) {
            int count = findFirstGreatOrEqual(a, x);
            ans.add(count);
        }
        return ans;
    }

    private int findFirstGreatOrEqual(int[] a, int x) {
        int left = 0, right = a.length - 1;
        int ans = a.length; // 这里需要注意:表示while循环里没有任何匹配,也就是ans没有被赋值过。 ==> a[i] < x
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (a[mid] >= x) {
                ans = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }

33. 搜索旋转排序数组

思路:难题。
旋转数组,精确查找,返回下标。
经验:画图演示时,在直角坐标系下面画图,后面视频中就是这么画的,方便理解。

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
  
示例 3:
输入:nums = [1], target = 0
输出:-1

代码:

    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = ((left + right) >>> 1);
            if (target == nums[mid]) {
                return mid;
            }
            // check [left, mid] is sorted
            if (nums[left] <= nums[mid]) {
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else { // [mid, right] is sorted
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }

81. 搜索旋转排序数组 II

153. 寻找旋转排序数组中的最小值

154. 寻找旋转排序数组中的最小值 II

69. x 的平方根

这个题目本质是求一个y使得 y*y <= x. y尽可能大一点。 可以认为是在有序的数组中,找最后一个满足条件的值。

class Solution {
    public int mySqrt(int x) { // time: O(log n)
        if (x <= 1) {
            return x;
        }
        int left = 1, right = x / 2, ans = 1;
        while (left <= right) {
            int mid = (left + right) >>> 1;
            if (mid <= x / mid) { // mid*mid <= x 时 继续往右寻找
                ans = mid;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return ans;
    }
}
posted @ 2023-05-21 09:15  编程爱好者-java  阅读(10)  评论(0编辑  收藏  举报