Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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;
    }
}

 

posted on 2015-03-13 11:05  NickyYe  阅读(181)  评论(0编辑  收藏  举报