Leetcode上几道Binary Search题需要注意的地方

Binary Search的原理很容易理解,但是每次脚标没算对弄得溢出或是死循环的时候总是很恼火。今天就把Leetcode上几道经典的利用Binary Search解的题放在一起来感受一下。


一般有几个比较容易迷糊的地方,

·1. 终止条件不同 是取 start < end 还是 start <= end

 2. 分半的时候是取mid, mid - 1, 还是mid + 1 (这里mid含义对应数组的index, 如果涉及到例如sqrt(x), pow(x,n)的数学问题时略有区别)


做了一些题,总体感觉,start<=end 一般是和 mid - 1 , mid + 1 对应的,否则有可能出现死循环

道理很简单我们做一个简单的心算就知道了,

当 Start == end 的,mid = start + (end - start)/2 = start + (start - start)/2 = start

Start, mid, end 其实为(start, start, start),3个Pointer指向一个元素,这样不管是mid - 1 还是 mid + 1 都能顺利跳出循环,如果分半里面有mid,就会出现死循环


所以如果分半的时候要用mid的时候,一般是不用写Start = end情况的题


我们还是用具体题来体会一下. (下面的题里面,我都用了start<=end, mid-1, mid+1组合)


Problem 1: Search a 2D Matrix

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

  • Integers in each row are sorted from left to right.
  • The first integer of each row is greater than the last integer of the previous row.

For example,

Consider the following matrix:

[
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]

Given target = 3, return true.

Solve the problem on Leetcode

分析:

这个题是最经典的Binary Search的题,看似矩阵,其实拉开就是一个数组

最开始的元素 index为0,最后一个元素 index 为 row * col -1

那么怎么从数组角标换算成矩阵角标呢,其实矩阵就是以col为个数的一个一个组,我们要知道这是第几个租(对应row)那当然除以col啦,想知道这是某个组中的第几个元素(对应col)当然看对col的余数咯

public class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int start = 0;
        int row = matrix.length;
        int col = matrix[0].length;
        int end = row * col - 1;
        while(start<=end){
            int mid = start + (end - start)/2;
            int i = mid/col;
            int j = mid%col;
            if (target == matrix[i][j]) return true;
            else if (target > matrix[i][j]) start = mid + 1;
            else end = mid - 1;
        }
        return false;
    }
}

Problem 2: Search insert position 

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

You may assume no duplicates in the array.

Here are few examples.
[1,3,5,6], 5 → 2
[1,3,5,6], 2 → 1
[1,3,5,6], 7 → 4
[1,3,5,6], 0 → 0

Solve the problem on leetcode

分析:

这道题是下面题的基础

A[mid] == target的时候就直接返回了这个很直观

需要分析的是A[mid] > target 和A[mid] < target的两种情况

拿我们之前说的情况来验证一下

start = end 时 (start, start, start)指向同一个元素,

最后target > A[mid] 时,start = mid + 1 正好插在新start的位置上

最后target < A[mid]时,end = mid - 1, 而此时start = start 正好需要放在Start的位置上

所以我们最后返回Start就是所需的结果 

public class Solution {
    public int searchInsert(int[] A, int target) {
        int start = 0;
        int end = A.length - 1;
        while(start<=end){
            int mid = start + (end - start)/2;
            if (A[mid] == target) return mid;
            else if (A[mid] > target) end = mid - 1;
            else start = mid + 1;
        }
        return start;
    }
}

Problem 3: Search for a Range

Given a sorted array of integers, find the starting and ending position of a given target value.

Your algorithm's runtime complexity must be in the order of O(log n).

If the target is not found in the array, return [-1, -1].

For example,
Given [5, 7, 7, 8, 8, 10] and target value 8,
return [3, 4].

Solve the problem on Leetcode

分析:

首先这道题里允许有Duplicates的存在,所以再发现相等的时候,我们不能直接返回index,我们要找到第一次出现target值的index

这道题的解题思路,其实来自于上一题,如果我们能找到 target 插入的位置和 target+1 插入的位置,那么我们不就知道范围了吗


具体需要分3种情况

1. target 存在,target + 1 存在  A[] is [.......6,7,7,7,7,8,8,8....] search 7 

我们只需要找到7最开始的位置和8最开始的位置。返回target + 1 位置的时候记得我们要在主程序里 - 1

2. target不存在,target + 1存在 A[] is [.......6,8,8,8.....] search 7

8我们找到了,那么7呢,我们用search insert position 的程序,返回6后面的一个位置,恰好和8重合,返回后因为target + 1位置要在主程序里 - 1, 这样会出现first Index > second Index的情况,在这种情况下我们便能判断出不存在

3. target不存在,target + 1不存在 A[] is [.....6,6,6,9,9....] search 7

用Search insert position的程序,target 和 target + 1 都返回第一个9的index, 返回后和情况2一样

public class Solution {
    public int[] searchRange(int[] A, int target) {
        int[] res = new int[2];
        res[0] = search(A,target);
        res[1] = search(A,target+1);
        res[1]--;
        if (res[0]>res[1]) {
            res[0] = -1;
            res[1] = -1;
        }
        return res;
    }
    
    public int search(int[] A, int target){
        int end = A.length - 1;
        int start = 0;
        while(start<=end){
            int mid = start + (end - start)/2;
            if (A[mid] == target){
                while(mid>0 && A[mid]==A[mid-1])
                        mid--;
                return mid;         
            }
            else if (A[mid] > target) end = mid - 1;
            else start = mid + 1;
        }
        return start;
    }
}

Problem 4: Search in Rotate Sorted Array I

Suppose a sorted array is rotated at some pivot unknown to you beforehand.

(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Solve the problem on Leetcode

分析:

和在一个Array直接进行Binary Search一样的思路,唯一的不同,当我们有start,mid,end 后,我们要判断哪一边是有序序列

如果左边是有序排列 (A[mid] > A[start]),我们就把Target和左边的两个边界做比较,看看在不在左边

如果右边是有序排列 (A[mid] < A[start]),我们就把Target和右边的两个边界做比较,看看在不在右边

有一个值得注意的问题,A[mid] 和A[start]比较的时候相等怎么办?

在这道题里,没有重复的元素,那么唯一的可能就是mid 和start都指向同一元素

举个列子 [1,3] 左边为[1] , 右边为[1,3],这个没问题哪边都行,那如果[3,1]呢?左边[3],右边[3,1],显然左边是有序的,所以这道题里我们把相等的情况归在A[mid] > A[start]里

public class Solution {
    public int search(int[] A, int target) {
        int start = 0;
        int end = A.length - 1;
        while(start<=end){
            int mid = start + (end - start)/2;
            if (A[mid]==target) return mid;
            if (A[mid] >= A[start]){
                if (target>=A[start] && target<=A[mid])
                    end = mid - 1;
                else start = mid + 1; 
            }
            else{
                if (target>=A[mid] && target<=A[end])
                    start = mid + 1;
                else end = mid - 1;
            }
        }
        return -1;
    }
}

Problem 5 : Search in Rotated Sorted Array II

Follow up for "Search in Rotated Sorted Array":
What if duplicates are allowed?

Would this affect the run-time complexity? How and why?

Write a function to determine if a given target is in the array.

Solve the problem on Leetcode

分析:

这道题和上一题一样,唯一的区别就是允许有Duplicates,同时返回的结果简化了,返回一个boolean就可以

不要想复杂了,要记住有序这个最重要的特性是不变的,所以我们要分析的还是A[mid]==A[start]的情况

情况1. 从 mid -> end -> start 都是同样值的元素

情况2.  mid和start指向同一个元素

当相等的时候,我们只需让start跳过那一个元素就可以了,即start++;

因为,程序里是先判断相等的,不等后再判断A[mid]和A[start]的值,所以跳过这些和start相等的值,不会影响最后的结果,一直能跳到我们能判断左边还是右边是有序的序列的位置

而对于第二种情况,start++, mid也要重新计算,比如[3,1]找2,跳一次以后start继续==mid 再跳,start超出end,结束循环,返回False

public class Solution {
    public boolean search(int[] A, int target) {
        int start = 0;
        int end = A.length - 1;
        while(start<=end){
            int mid = start + (end - start)/2;
            if (A[mid] == target) return true;
            if (A[mid]>A[start]){
                if (target>=A[start] && target<=A[mid])
                    end = mid - 1;
                else start = mid + 1;
            }
            else if (A[mid]<A[start]){
                if (target>=A[mid] && target<=A[end])
                    start = mid + 1;
                else end = mid - 1;
            }
            else start++; // 区别就在于这一句话
        }
        return false;
    }
}

说了这么多,只要把Special Case想清楚了,就能避开很多容易错的情况。下一篇会总结Binary Search在一些数学题里的应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值