统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
题目很简单, 做起来也确实容易, 但是面试官肯定不是想要暴力法的, 而且思路很多, 要是能说出来几种不同的思路, 绝对会让人眼前一亮的.
1. 暴力法, 从开始遍历, 如果相等, 就把result+=1, 直到结束. 最原始的暴力法, 有一个小地方可以优化, 就是结束的判定条件, 由于是递增的数组, 只要有nums[i]>target , 那么后续肯定也不会有相同的target了, 这个可以提前结束循环.
class Solution {
func search(_ nums: [Int], _ target: Int) -> Int {
var result = 0
for i in 0..<nums.count {
if nums[i]==target {
result += 1
}
if nums[i]>target {
break;
}
}
return result
}
}
2. 可以先用二分查找找到target, 然后在向左向右探测找到左右的边界. 思路倒是很朴素,代码量有点大.
class Solution {
func search(_ nums: [Int], _ target: Int) -> Int {
var index = -1
var left = 0
var right = nums.count
// 二分查找找出任意一个target的位置
while left<right {
let mid = (left+right)/2
if nums[mid] == target {
index = mid
break
}
if nums[mid]>target {
right = mid
} else if nums[mid]<target {
left = mid+1
}
}
// 这个数字不存在
if index == -1 {
return 0
}
print("找到了\(index)")
// 向左找最左边的target
var leftIndex = 0
while index-leftIndex>=0 {
if index-leftIndex-1<0 {
break
}
if nums[index-leftIndex] == target && nums[index-leftIndex-1] != target {
print("在左边找到了\(leftIndex)")
break
}
leftIndex += 1
}
// 向右找最右边的target
var rightIndex = 0
while index+rightIndex<nums.count {
if index+rightIndex+1>=nums.count {
break
}
if nums[index+rightIndex] == target && nums[index+rightIndex+1] != target {
print("在右边找到了\(rightIndex)")
break
}
rightIndex += 1
}
return leftIndex + rightIndex + 1
}
}
3. 基于上面的优化, 一开始用二分查找找出最左边的target, 然后只需要向右探测找出最右边的即可. 需要在原来的二分查找上略作修改.
class Solution {
func search(_ nums: [Int], _ target: Int) -> Int {
var index = -1
var left = 0
var right = nums.count
// 二分查找找出最左边的target的位置
while left<right {
let mid = (left+right)/2
// 保证index是最左边的target, 左边的值 != target || 左边没有值了
if nums[mid] == target {
// 左边的值 != target
if mid-1>=0 && nums[mid-1] != target {
index = mid
break
}
// 左边没有值了
if mid-1<0 {
index = mid
break
}
}
if nums[mid]>=target {
right = mid
} else {
left = mid+1
}
}
// 这个数字不存在
if index == -1 {
return 0
}
print("找到了\(index)")
// 向右找最右边的target
var rightIndex = 0
while index+rightIndex<nums.count {
// 右边没有值了
if index+rightIndex+1>=nums.count {
break
}
// 右边的值 != target
if nums[index+rightIndex] == target && nums[index+rightIndex+1] != target {
print("在右边找到了\(rightIndex)")
break
}
rightIndex += 1
}
return rightIndex + 1
}
}
4. 上面已经可以通过2分查找找到最左边的target了, 自然也可以类似的找到最右边的target, 然后最右边的target - 最左边的target+1 就是结果了.
class Solution {
func search(_ nums: [Int], _ target: Int) -> Int {
var leftIndex = -1
var left = 0
var right = nums.count
// 二分查找找出最左边的target的位置
while left<right {
let mid = (left+right)/2
// 保证index是最左边的target, 左边的值 != target || 左边没有值了
if nums[mid] == target {
// 左边的值 != target
if mid-1>=0 && nums[mid-1] != target {
leftIndex = mid
break
}
// 左边没有值了
if mid-1<0 {
leftIndex = mid
break
}
}
if nums[mid]>=target {
right = mid
} else {
left = mid+1
}
}
// 这个数字不存在
if leftIndex == -1 {
return 0
}
print("找到了\(leftIndex)")
var rightIndex = leftIndex
left = leftIndex
right = nums.count
// 二分查找找出最右边的target的位置
while left<right {
let mid = (left+right)/2
// 保证index是最右边的target, 右边的值 != target || 右边没有值了
if nums[mid] == target {
// 右边的值 != target
if mid+1<=nums.count-1 && nums[mid+1] != target {
rightIndex = mid
break
}
// 右边没有值了
if mid+1>=nums.count {
rightIndex = mid
break
}
}
// 此处是>,可以保证找到最右边, 而上面是>=,是为了找最左边
if nums[mid]>target {
right = mid
} else {
left = mid+1
}
}
return rightIndex-leftIndex+1
}
}
5. 在看题解的过程中, 发现了一种很巧妙的思路, 由于传入的是一个int数组, 现在假设要往这个数组中插入新的值,那么target-0.5要插入的位置就是最左边的target位置, target+0.5的位置就是最右边的target的位置+1, 用(target+0.5) - (target-0.5)的位置差就是target的数量.
class Solution {
func search(_ nums: [Int], _ target: Int) -> Int {
// 相当于左边界的位置
let leftInsert = insert(nums, Double(target)-0.5 )
// 相当于右边界+1的位置
let rightInsert = insert(nums, Double(target)+0.5 )
return rightInsert-leftInsert
}
// 假设要在nums中插入一个数字, 找出要插入数字的位置
func insert(_ nums: [Int], _ target: Double) -> Int {
var left = 0
var right = nums.count-1
while left <= right {
let mid = (left+right)/2
if target > Double(nums[mid]) {
left = mid+1
}
if target < Double(nums[mid]) {
right = mid-1
}
}
// left就是要插入值的位置
return left
}
}
总结一下目前的思路,
1.遍历过程中统计次数,nums[i]>target可以提前结束,O(N)
2.二分查找出任意一个target的位置,然后左右扩展寻找,O(logN)
3.二分查找出最左边的target位置,然后向右扩展寻找,O(logN)
4.二分找出最左边的位置,二分找出最右边的位置,最右-最左+1即是结果,O(logN)
5.找出target+0.5,target-0.5的插入位置,O(logN)
实际在运行的过程中, 除了第一个比较慢一点外, 其余的4种算法基本都是56ms, 可以打败99%.