Search for a Range

https://leetcode.com/problems/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].

解题思路:

看到O(logn),就知道这题仍然是比较典型的二分查找。二分查找想要写正确的关键就在于把握分类的原则,就是方法内部所有分支都没有交集,但所有分支加起来又正好是全集。这个原则非常关键,有助于写出清晰的程序。前面讲过,如果前者违背了,可能导致mid被多次更新,有些区间就搜不到了;后者被违背可能搜到无法更新start或end的区间,引起死循环。我们来看看这道题目。

A[mid]大于或者小于taget的情况下,去搜另一边,这个和普通的二分查找没什么区别。问题是,如果A[mid]==target,就能return吗?这里是不行的,必须分别往两边搜,找出target可能的最大区域。但是,这时已经可以更新区域的边界值了,这里用一个int[] result来记录。分别存储mid的最大和最小值即可。但是注意第一次更新最小值的时候不能直接Math.min(result[0], mid),因为-1永远最小,必须强行更新。

递归终止的条件是start > end。

有了上面的思路,这段代码可以非常清晰的写出,一次AC,鼓舞人心啊。刷题到现在可以感觉自己的coding能力在提高。

public class Solution {

    public int[] searchRange(int[] A, int target) {

        int[] result = new int[]{-1, -1};

        searchRangeHelper(A, 0, A.length - 1, target, result);

        return result;

    }

    

    public void searchRangeHelper(int[] A, int start, int end, int target, int[] result){

        int mid = start + (end - start) / 2;

        if(start > end){

            return;

        }

        if(A[mid] < target){

            searchRangeHelper(A, mid + 1, end, target, result);

        }

        if(A[mid] > target){

            searchRangeHelper(A, start, mid - 1, target, result);

        }

        if(A[mid] == target){

            if(result[0] == -1){

                result[0] = mid;

            }else{

                result[0] = Math.min(result[0], mid);

            }

            result[1] = Math.max(result[1], mid);

            searchRangeHelper(A, start, mid - 1, target, result);

            searchRangeHelper(A, mid + 1, end, target, result);

        }

    }

}

下面是网上有人给出的算法,方法和我的一样,为什么贴出来呢?是因为它用了递归,但其实思路是比较混乱的,因为递归内部还用了循环。循环一般是迭代时候采用的退出条件,而递归一般在进入方法的时候判断,如果l<=r的时候就return了。所以他这不是一个好方法,贴出来纯粹为了举反例,告警自己。

public class Solution {

    public int[] searchRange(int[] A, int target) {

        int[] ans = new int[]{-1,-1};

        searchRange(A, target, 0, A.length-1, ans);

        return ans;

    }



    private void searchRange(int[] A,  int target, int start, int end, int[] ans){

        if(end<start) return;

        int l =start, r = end, mid;

        while(l<=r){

            mid = l+(r-l)/2;

            if(A[mid]==target){

                ans[0] = (ans[0]>=0)? Math.min(ans[0],mid):mid;

                ans[1] = Math.max(ans[1],mid);

                searchRange(A,target,l,mid-1,ans);

                searchRange(A,target,mid+1,r,ans);

                return;

            }

            else if(A[mid]>target) r = mid-1;

            else l = mid+1;   

        }

        return;          

    }

}

回头说这道题的解法,二分查找可以用递归可以用迭代,一般简单的我们都用迭代,可是为什么这道题用递归?因为A[mid] == target的时候,需要分别去两边搜索,这时递归就能写出比较清晰的思路。那么用迭代如何去实现呢?两次二分查找。第一次找range的最左边界,第二次找最右边界。

public class Solution {

    public int[] searchRange(int[] A, int target) {

        int[] result = new int[]{-1, -1};

        

        int start = 0;

        int end = A.length - 1;

        while(start <= end){

            int mid = start + (end -start) / 2;

            if(A[mid] < target){

                start = mid + 1;

            }else if(A[mid] > target){

                end = mid - 1;

            }else{

                if(result[0] == -1){

                    result[0] = mid;

                }else{

                    result[0] = Math.min(result[0], mid);

                }

                result[1] = Math.max(result[1], mid);

                //找最左边界

                end = mid - 1;

            }

        }

        

        start = 0;

        end = A.length - 1;

        while(start <= end){

            int mid = start + (end -start) / 2;

            if(A[mid] < target){

                start = mid + 1;

            }else if(A[mid] > target){

                end = mid - 1;

            }else{

                if(result[0] == -1){

                    result[0] = mid;

                }else{

                    result[0] = Math.min(result[0], mid);

                }

                result[1] = Math.max(result[1], mid);

                //找最右边界

                start = mid + 1;

            }

        }

        return result;

    }

}

继续优化,第一次更新最左边界,那么只要更新result[0]就可以了。第二次更新最右边界,再去更新result[1]。而且第二次只需要在result[0]到A.length - 1的区间内去搜索右边界即可。但是要注意,如果result[0]==-1,则要从0开始。

public class Solution {

    public int[] searchRange(int[] A, int target) {

        int[] result = new int[]{-1, -1};

        

        int start = 0;

        int end = A.length - 1;

        while(start <= end){

            int mid = start + (end -start) / 2;

            if(A[mid] < target){

                start = mid + 1;

            }else if(A[mid] > target){

                end = mid - 1;

            }else{

                if(result[0] == -1){

                    result[0] = mid;

                }else{

                    result[0] = Math.min(result[0], mid);

                }

                //找最左边界

                end = mid - 1;

            }

        }

        

        start = result[0] == -1 ? 0 : result[0];

        end = A.length - 1;

        while(start <= end){

            int mid = start + (end -start) / 2;

            if(A[mid] < target){

                start = mid + 1;

            }else if(A[mid] > target){

                end = mid - 1;

            }else{

                result[1] = Math.max(result[1], mid);

                //找最右边界

                start = mid + 1;

            }

        }

        return result;

    }

}

 

你可能感兴趣的:(search)