这是我参与11月更文挑战的18天,活动详情查看:2021最后一次更文挑战」。
前言
一直都计划学习数据结构与基本算法,但是平时都看一阵停一阵。现在决心坚持下去,我准备从LeetCode的HOT100开始,每天完成1~2道习题,希望通过这种方式养成持续学习的习惯。因为我是做iOS开发的,主要是用Objective-C语言,最近也在学习Swift,所以本系列的题解都将使用swift语言完成,本文更新的是LeetCode中HOT100的第18题034 在排序数组中查找元素的第一个和最后一个位置。
题目
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
1. 0 <= nums.length <= 105
2. -109 <= nums[i] <= 109
3. nums 是一个非递减数组
4. -109 <= target <= 109
分析
直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 target 的下标,但这个方法的时间复杂度为 O(n),没有利用到数组升序排列的条件。
由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。
二分查找法的基本思路如下:
1. 设置left、right两个指针分别指向数组的两端
2. 取left、right中间下标mid = (left + right) / 2,然后判断nums[mid]与target的大小关系:
2.1 如果nums[mid] == target,则返回对应的mid即可
2.2 如果nums[mid] > target,则target只有可能在mid的左边,即将right赋值为mid-1,继续查找
2.3 如果nums[mid] < target,则target只有可能在mid的右边,即将left赋值为mid+1,继续查找
2.4 如果一直找到left > right都没有找到,则表明数组中不存在目标值,返回 -1
上述的方法中我们只能找到第一个与目标值相等的下标,所以,为了要找出等于target的最左边和左右边的下标,我们需要进行改造,改造方式就是:
- 如果要查找最左边的下标,则在nums[mid] == target时,记录当前下标,并且继续在mid左侧继续进行查找,如果找到则更新目标下标,这样最后的下标才是最左边满足目标值的下标。
- 如果要查找最右边的下标,则在nums[mid] == target时,记录当前下标,并且继续在mid右侧继续进行查找,如果找到则更新目标下标,这样最后的下标才是最右边满足目标值的下标。
题解
class KLLC034 {
func searchRange(_ nums: [Int], _ target: Int) -> [Int] {
if nums.isEmpty {
return [-1, -1]
}
let targetLeft = binarySearch(nums, target, true)
let targetRight = binarySearch(nums, target, false)
return [targetLeft, targetRight]
}
/// 二分查找目标值
/// - nums: 待查找有序数组
/// - target: 查找目标值
/// - isLeft: 是否是目标值最小的下标
/// - Returns: 目标值的下标,不存在则返回-1
func binarySearch(_ nums: [Int], _ target: Int, _ isLeft:Bool) -> Int {
//初始化目标下标targetIndex为-1,left、right指向数组两端
var targetIndex = -1
var left = 0, right = nums.count - 1
while left <= right {
//取中间下标
let mid = (left + right) >> 1
if nums[mid] == target {
//满足目标值则更新目标下标
targetIndex = mid
if isLeft {
//如果是查找最左边的下标,则继续在左侧查找
right = mid - 1
} else {
//如果是查找最右边的下标,则继续在右侧查找
left = mid + 1
}
} else if nums[mid] > target {
right = mid - 1
} else {
left = mid + 1
}
}
return targetIndex
}
}