力扣题目链接[1]
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
提示:
思路:
首先考虑通过遍历数组进行查找重复次数。该方法没有合理利用有序数组的前提条件,适合无序数组的元素统计。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let i = 0;
let j = 0;
while(i < nums.length) {
if (nums[i] === target) j++;
i++;
}
return j;
};
首先题目描述中指出,数组是非递减数组,因此重复元素肯定是相邻的。遇到有序数组,可以考虑使用二分法解决。
平常使用二分法用来判断某个元素是否存在于排序数组中,但是无法有效的获取重复的个数。因此需要在二分法的基础上进一步确定左右边界。
我们规定:最终的左区间和右区间是紧邻重复的目标元素,也就是目标元素位于(left, right)
开区间内。因此最后返回right - left - 1
。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let i = 0;
let j = nums.length - 1;
// 确定右边界
while(i <= j) {
let middle = Math.floor(i + (j - i) / 2);
if (nums[middle] <= target) {
i = middle + 1;
} else {
j = middle - 1;
}
}
let right = i;
i = 0;
// 如果j所在位置不是目标值,则意味着目标值不存在,直接返回0
if (j >= 0 && nums[j] !== target) return 0;
// 确定左边界
while(i <= j) {
let middle = Math.floor(i + (j - i) / 2);
if (nums[middle] < target) {
i = middle + 1;
} else {
j = middle - 1;
}
}
let left = j;
return right - left - 1;
};
分析:
首先通过二分法确定出右边界,由于最后一次满足条件会执行 i = middle + 1
,因此i
就是右边界。
此处有一个优化条件,那就是第一次二分之后,j
所在位置如果不是目标值,则意味着目标值不存在。那是因为最后一次会执行j = middle - 1
。如果目标值存在,则j
刚好是最后一个目标值所在位置。
第二次二分用来确定左边界。注意此处的判断条件,当中间值大于等于目标值时,都会执行j = middle - 1
。因此最后一次执行的时候,就会执行此语句。所以j
就是左边界。
最后通过相减得到重复的次数。
该题属于二分法的进阶版,通过两次二分法分别来确定左右边界。最后需要注意边界的处理。
[1]力扣题目链接: https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5874p1/