【LeetCode】No.34 Search for a Range

来源:互联网 发布:淘宝京润珍珠是真的吗 编辑:程序博客网 时间:2024/06/15 23:30

【原题】

Given an array of integers sorted in ascending order, 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].

【翻译】

数字在排序数组中出现的次数。

给定一个排好序的数组和一个数字,输出该数字出现的起始位置和结束位置,以及出现的次数(即:结束位置-起始位置+1)。

【解题思路】

排好序的数组→二分查找。

改进的二分查找:

二分查找算法总是先拿数组中间的数字和k作比较。如果中间的数字比k大,那么k只有可能出现在数组的前半段,下一轮我们只在数组的前半段查找就可以了。如果中间的数字比k小,那么k只有可能出现在数组的后半段,下一轮我们只在数组的后半段查找就可以了。如果中间的数字和k相等呢?我们先判断这个数字是不是第一个k。如果位于中间数字的前面一个数字不是k,此时中间的数字刚好就是第一个k。如果中间数字的前面一个数字也是k,也就是说第一个k肯定在数组的前半段,下一轮我们仍然需要在数组的前半段查找。

二分法在数组中查找一个合乎要求的数字时间复杂度是O(logn),因此总的时间复杂度也只有O(logn)。

==============================================================================================

伪代码1:找到开头的k

1、一旦发现start>end,就说明数组中根本没有K,所以返回-1表示出错;

2、计算中点位置,将中点的值和k进行比较:

2.1. 若k小于中点值,则在前半段找

2.2. 若k大于中点值,则在后半段找

2.3. 若k等于中点值:

a)若中点已经是数组第一个元素,则直接返回中点位置;

b)若中点值的前一个元素和中点值不同,则直接返回中点位置;

c)若中点值前一个元素和中点值相同,则继续在前半段找;

3、计算出新的start、end后继续递归。

伪代码2:主函数

1、健壮性判断:若数组为空、数组的第一个元素大于k、最后一个元素小于k,则根本不存在k,直接返回0.

2、分别计算k开头位置、结尾位置

3、只要其中一个为-1则返回-1;否则返回(结尾-开头+1)

===============================================================================================

(1)递归:每次的前半段、后半段有重复的操作,所以用递归,但是每次操作的数组范围不一样,所以应该再加两个参数传入start和end。

class Solution {public:    //找起始位置    int GetFirstTarget(vector<int>& nums, int target, int start, int end){        if(start > end){            return -1;        }        int mid = (start+end)/2;        //当nums[mid]=target,又分三种情况        if(nums[mid] == target){            if((mid>0 && nums[mid-1] != target) || mid == 0){//当mid不是第一个且左邻居不同(防止越界) 或者 mid就是数组第一个元素                return mid;            }            else{//否则在前半段继续找第一次出现的位置                end = mid-1;            }        }        //当nums[mid]>target,在前半段找        else if(nums[mid] > target){            end = mid-1;        }        //当nums[mid]<target,在后半段找        else{            start = mid+1;        }        return GetFirstTarget(nums, target, start, end);//缩小要寻找的范围进行递归    }        //找结束位置    int GetLastTarget(vector<int>& nums, int target, int start, int end){        if(start > end){            return -1;        }        int mid = (start+end)/2;        //当nums[mid]=target,又分三种情况        if(nums[mid] == target){            if((mid<(nums.size()-1) && nums[mid+1] != target) || mid == nums.size()-1){//当mid不是最后一个且右邻居不同(防止越界) 或者 mid就是数组最后一个元素                return mid;            }            else{//否则在后半段继续找最后一次出现的位置                start = mid+1;            }        }        //当nums[mid]>target,在前半段找        else if(nums[mid] > target){            end = mid-1;        }        //当nums[mid]<target,在后半段找        else{            start = mid+1;        }        return GetLastTarget(nums, target, start, end);//缩小要寻找的范围进行递归    }        vector<int> searchRange(vector<int>& nums, int target) {        vector<int> result;        if(nums.size() <= 0){            result.push_back(-1);            result.push_back(-1);        }        else{            int firstNum = GetFirstTarget(nums, target, 0, nums.size()-1);            int lastNum = GetLastTarget(nums, target, 0, nums.size()-1);            result.push_back(firstNum);            result.push_back(lastNum);        }        return result;    }};

2)非递归:超时!

//找起始位置    int GetFirstTarget(vector<int>& nums, int target, int start, int end){        if(start > end){            return -1;        }        int mid = (start+end)/2;        while(start <= end){            //当nums[mid]=target,又分三种情况            if(nums[mid] == target){                if((mid>0 && nums[mid-1] != target) || mid == 0){//当mid不是第一个且左邻居不同(防止越界) 或者 mid就是数组第一个元素                    return mid;                }                else{//否则在前半段继续找第一次出现的位置                    end = mid-1;                }            }            //当nums[mid]>target,在前半段找            else if(nums[mid] > target){                end = mid-1;            }            //当nums[mid]<target,在后半段找            else{                start = mid+1;            }        }//while    }

【O(n)的解法】

1、遍历一次,直接找,设置一个标记用来保证不移动start只移动end。可是时间为O(n),不符合。

class Solution {public:    vector<int> searchRange(vector<int>& nums, int target) {        int start=-1, end = -1;        for(int i=0; i<nums.size(); i++){            if(nums[i]==target && start == -1){                start = i;                end = i;            }            else if(start != -1 && nums[i] == target){                end += 1;            }        }        vector<int> v;        v.push_back(start);        v.push_back(end);        return v;    }};
2、若直接运用二分查找:给出的例子中,可以先用二分查找算法找到一个3。由于3可能出现多次,因此我们找到的3的左右两边可能都有3,于是在找到的3的左右两边顺序扫描,分别找出第一个3和最后一个3。因为要查找的数字在长度为n的数组中有可能出现O(n)次,所以顺序扫描的时间复杂度是O(n)。因此这种算法的效率和直接从头到尾顺序扫描整个数组统计3出现的次数的方法是一样的。
class Solution {public:    vector<int> searchRange(vector<int>& nums, int target) {        vector<int> result;        int low = 0, high = nums.size()-1;        int mid;        while(low <= high){            mid = (low+high)/2;            if(nums[mid] == target) break;            else if(nums[mid] > target){ high = mid-1; }            else { low = mid+1; }        }//找不到跳出循环此时low>high,返回{-1,-1};找到了也跳出循环此时要判断nums[mid]周围情况                //往mid两边顺序扫描,直到找到起始位置和解释位置,最坏为O(n)        if(low <= high){            low = mid-1;//把low放在mid的左邻居,看nums[mid-1]等不等于target            while(low >=0 && nums[low] == target){//条件1:如果mid=0是数组的第一个元素;条件2:mid左边也是target                low--;            }            high = mid+1;            while(high < nums.size() && nums[high] == target){//条件1:如果mid=nums.size()-1是数组的最后一个元素;条件2:mid右边也是target                high++;            }            result.push_back(low + 1);            result.push_back(high - 1);        }        else{            result.push_back(-1);            result.push_back(-1);        }        return result;    }};
0 0
原创粉丝点击