1、描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如 :数组[0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回-1。
你可以假设数组中不存在重复元素,你的算法时间复杂度必须是O(logn)级别。
例1:输入:nums = [4, 5, 6, 7, 0, 1, 2],target = 0
输出:4
例2:输入:nums = [4, 5, 6, 7, 0, 1, 2],target = 3
输出:-1
2、算法
由于题目中明确了时间复杂度,所以只能采用二分查找法
1)直接用二分法,判断二分点的位置
func search3(_ nums: [Int], _ target: Int) -> Int {
//直接用二分法,判断二分点-----超出时间限制
let n = nums.count
if n==0 {
return -1
}
var left = 0
var right = n-1
while left <= right {
let mid = left+(right-left)/2
//如果中间值小于第一个数,目标值也小于第一个数,取中间值
let num = (nums[mid]<nums[0]) == (target<nums[0]) ? nums[mid] : target<nums[0] ? Int.min : Int.max
if num < target {
//中间值小于目标值,在中间值的右边查找
left = mid+1
}else if num > target {
//中间值大于目标值,在中间值的左边查找
right = mid
}else {
//中间值等于目标值,返回中间值下标
return mid
}
}
// print(left, right)
return -1
}
2)直接用二分法的方法2
func search2(_ nums: [Int], _ target: Int) -> Int {
//直接用二分法,判断二分点
/*
1、直接等于target
2、在左半边递增区域
1)target 在 left 和 mid 之间
2)不在之间
3、在右边的递增区域、
1)target在mid 和 right 之间
2)不在之间
*/
let n = nums.count
if n==0 {
return -1
}
var left = 0
var right = n-1
while left < right {
let mid = left+(right-left)/2
if nums[mid] == target {
return mid
}else if nums[mid] >= nums[left]{//二分点在左半边递增区域
if nums[left] <= target && target < nums[mid] {
//target位于 left 和 mid 之间
right = mid-1
}else{
left = mid+1
}
}else if nums[mid] < nums[right]{
//二分点在右边的递增区域
if nums[mid] < target && target <= nums[right] {
left = mid+1
}else{
right = mid-1
}
}
}
// print(left, right)
return nums[left] == target ? left : -1
}
3)二分查找法,先找到分割点,再查找目标值
func search1(_ nums: [Int], _ target: Int) -> Int {
//二分查找法:先找到数组中最小的数 即分割点
let n = nums.count
if n==0 {
return -1
}
var left = 0
var right = n-1
while left < right {
let mid = left + (right-left)/2
if nums[mid] > nums[right] {
//如果中间的数大于右边的数,从mid的右边查找
left = mid+1
}else{
right = mid
}
}
//分割点下标
let split_t = left
left = 0
right = n-1
//判断分割点在target的左边还是右边
if nums[split_t] <= target && target <= nums[right] {
//分割点在target的右边
left = split_t
}else{
//分割点在target的左边
right = split_t
}
while left <= right {
let mid = left + (right-left)/2
if nums[mid] == target {
//中间值等于目标值
return mid
}else if nums[mid] > target {
//中间值大于目标值,右边下标左移一位
right = mid - 1
}else{
//反之,左边下标右移一位
left = mid+1
}
}
return -1
}
4)二分查找法:先找到数组中最小的数 即发生旋转的下标,分3个函数
func search(_ nums: [Int], _ target: Int) -> Int {
//二分查找法:先找到数组中最小的数 即发生旋转的下标
let n = nums.count
if n==0 {
return -1
}
if n==1 {
return nums[0]==target ? 0 : -1
}
let rotate_index = find_rotate_index(nums, 0, n-1)
//如果target是最小的值
if nums[rotate_index] == target {
return rotate_index
}
//如果数组并没有旋转,需要查找整个数组
if rotate_index == 0 {
return search(nums, target, 0, n-1)
}
if target < nums[0] {
//如果目标值小于第一个数,在旋转下标的右边查找
return search(nums, target, rotate_index, n-1)
}else{
return search(nums, target, 0, rotate_index)
}
}
//找到旋转的下标
private func find_rotate_index(_ nums : [Int], _ left: Int, _ right : Int)->Int{
var left = left
var right = right
if nums[left] < nums[right] {
//数组未发生旋转
return 0
}
//二分查找
while left <= right {
var pivot = (left+right)/2
if nums[pivot] > nums[pivot+1] {
//基准数>基准后一位,往右边查找
return pivot+1
}else{
//如果基准数小于左边左边的数,则右下标左移一位,反之,左下标右移一位
if nums[pivot]<nums[left]{
right = pivot-1
}else{
left = pivot+1
}
}
}
return 0
}
//根据下标二分查找
private func search(_ nums : [Int], _ target : Int, _ left: Int, _ right : Int)->Int{
var left = left
var right = right
while left <= right {
var pivot = (left+right)/2
if nums[pivot]==target {
return pivot
}else{
if target < nums[pivot] {
right = pivot-1
}else{
left = pivot+1
}
}
}
return -1
}
5)调测
调用:
var num3 = [4, 5, 6, 7, 0, 1, 2]
print(search(num3, 0), search(num3, 3))
print(search1(num3, 0), search1(num3, 3))
print(search2(num3, 0), search2(num3, 3))
print(search3(num3, 0), search3(num3, 3))
运行结果:
4 -1
4 -1
4 -1
4 -1