剑指offer系列——37.数字在排序数组中出现的次数/在排序数组中查找元素的第一个和最后一个位置
Q:统计一个数字在排序数组中出现的次数。
A:
1.用STL库函数解决
(1)count函数:algorithm头文件定义,其功能类似于find。这个函数使用一对迭代器和一个值做参数,返回这个值出现次数的统计结果。
public:
int GetNumberOfK(vector<int> data ,int k) {
return count(data.begin(),data.end(),k);
}
或
(2)equal_range函数:函数equal_range()返回first和last之间等于val的元素区间.返回值是一对迭代器。此函数假定first和last区间内的元素可以使用<操作符或者指定的comp执行比较操作.
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
auto resultPair = equal_range(data.begin(), data.end(),k);
return resultPair.second - resultPair.first;
}
};
2.二分查找
P.S.我有一个疑问,题目提的是排序,没说升序还是降序……二分法不需要确定一下吗??还是先sort一下比较好吧。
后面就是直接找range_left和range_right就好。
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int length = array.length;
if(length == 0){
return 0;
}
int firstK = getFirstK(array, k, 0, length-1);
int lastK = getLastK(array, k, 0, length-1);
if(firstK != -1 && lastK != -1){
return lastK - firstK + 1;
}
return 0;
}
//递归写法
private int getFirstK(int [] array , int k, int start, int end){
if(start > end){
return -1;
}
int mid = (start + end) >> 1;
if(array[mid] > k){
return getFirstK(array, k, start, mid-1);
}else if (array[mid] < k){
return getFirstK(array, k, mid+1, end);
}else if(mid-1 >=0 && array[mid-1] == k){
return getFirstK(array, k, start, mid-1);
}else{
return mid;
}
}
//循环写法
private int getLastK(int [] array , int k, int start, int end){
int length = array.length;
int mid = (start + end) >> 1;
while(start <= end){
if(array[mid] > k){
end = mid-1;
}else if(array[mid] < k){
start = mid+1;
}else if(mid+1 < length && array[mid+1] == k){
start = mid+1;
}else{
return mid;
}
mid = (start + end) >> 1;
}
return -1;
}
Q:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
A:
分别找左和右边界。
复习二分查找。
寻找左侧边界的⼆分查找:
因为我们初始化 right = nums.length,所以决定了我们的「搜索区间」是 [left, right),所以决定了 while (left < right)
同时也决定了 left = mid + 1 (left不可取mid)和 right = mid(right可取mid)
因为我们需找到 target 的最左侧索引,所以当 nums[mid] == target 时不要立即返回,而要收紧右侧边界以锁定左侧边界,最后相等时left=right,取[left,right)。
寻找右侧边界的⼆分查找
因为我们初始化 right = nums.length,所以决定了我们的「搜索区间」是 [left, right),所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid,因为我们需找到 target 的最右侧索引,所以当 nums[mid] == target 时不要⽴即返回,⽽要收紧左侧边界以锁定右侧边界
⼜因为收紧左侧边界时必须 left = mid + 1,所以最后⽆论返回 left 还是 right,必须减⼀
代码:
public int[] searchRange(int[] nums, int target) {
int[] result = new int[2];
Arrays.fill(result, -1);
if (nums.length == 0)
return result;
//取出来的边界是[left,right)
int leftRound = leftRound(nums, target);
int rightRound = rightRound(nums, target);
if (rightRound == 0 || leftRound == nums.length)//右边界为-1或左边界为length
return result;
if (nums[leftRound] != target)
return result;
result[0] = leftRound;
result[1] = rightRound - 1;
return result;
}
private int rightRound(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target)
left = mid + 1;
else if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
return right;//右边界取不到
}
private int leftRound(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target)
right = mid;
else if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
return left;//左边界可取到
}