搜索旋转排序数组
题目要求
给定一个升序排列的整数数组nums
,该数组在某个未知下标k
处进行了旋转。旋转后的数组形式为[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
。现在,给定一个目标值target
,要求编写一个时间复杂度为O(log n)
的算法,查找target
是否存在于数组nums
中,如果存在,返回其下标;如果不存在,返回-1
。
解题思路
由于题目要求算法的时间复杂度为O(log n)
,我们可以联想到二分查找算法,它符合这个时间复杂度要求。但是,由于数组经过了旋转,直接应用二分查找的常规方法是不可行的。我们需要对二分查找进行适当的修改,以适应旋转数组的特点。
解题步骤如下:
-
确定旋转点: 首先,我们需要确定数组的旋转点。由于数组原本是升序的,旋转后,数组被分为两个升序的子数组。我们可以通过比较中间元素与数组两端的元素来判断中间元素属于哪个子数组。
-
二分查找: 在确定了旋转点的情况下,我们可以判断
target
可能位于哪个子数组中。然后对可能包含target
的子数组应用二分查找。 -
判断条件: 在二分查找的每一步中,我们需要判断中间元素与
target
的关系,以及中间元素与数组两端元素的关系,来决定搜索范围是在左半部分还是右半部分。 -
循环或递归: 我们可以使用循环或递归的方式来实现二分查找,直到找到
target
或者确定target
不存在于数组中。 -
返回结果: 如果在某次二分查找中找到了
target
,则返回其下标;如果直到搜索范围为空都没有找到target
,则返回-1
。
通过以上步骤,我们可以在O(log n)
的时间复杂度内解决这个问题。需要注意的是,由于数组中的值互不相同,这简化了判断条件,因为我们不需要考虑重复元素带来的复杂性。
Golang 版本
package main
import "fmt"
func search(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
}
// 判断mid是在左段还是右段
if nums[mid] >= nums[left] {
// 判断target是在mid的左边还是右边,来调整left和right
if target >= nums[left] && target < nums[mid] {
right = mid - 1
} else {
left = mid + 1
}
} else {
// 同上,根据情况调整left和right
if target > nums[mid] && target <= nums[right] {
left = mid + 1
} else {
right = mid - 1
}
}
}
return -1
}
func main() {
nums := []int{4,5,6,7,0,1,2}
target := 0
result := search(nums, target)
fmt.Printf("The target %d's index is: %d\n", target, result)
}
这段代码实现了题目要求的功能,使用了二分查找的变种来适应旋转数组的情况。在每次循环中,它首先判断中间元素的位置,然后根据target
的值调整搜索范围,直到找到target
或者确定target
不存在。
C++版本
#include <iostream>
#include <vector>
int search(const std::vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
// 判断mid是在左段还是右段
if (nums[mid] >= nums[left]) {
// 判断target是在mid的左边还是右边,来调整left和right
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
// 同上,根据情况调整left和right
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
int main() {
std::vector<int> nums = {4, 5, 6, 7, 0, 1, 2};
int target = 0;
int result = search(nums, target);
std::cout << "The target " << target << "'s index is: " << result << std::endl;
return 0;
}
这段 C++代码实现了题目要求的功能,采用了二分查找算法来处理旋转排序数组中的搜索问题。代码首先确定搜索的左右边界,然后在循环中不断地将搜索范围缩小,直到找到目标值或者搜索范围为空。
Python 版本
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 判断mid是在左段还是右段
if nums[mid] >= nums[left]:
# 判断target是在mid的左边还是右边,来调整left和right
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
# 同上,根据情况调整left和right
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
# 测试代码
if __name__ == "__main__":
nums = [4, 5, 6, 7, 0, 1, 2]
target = 0
result = search(nums, target)
print(f"The target {target}'s index is: {result}")
这段 Python 代码实现了题目要求的功能,使用二分查找算法来处理旋转排序数组中的搜索问题。代码通过不断调整搜索的左右边界来缩小搜索范围,直到找到目标值或者确定目标值不在数组中。
Java 版本
public class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
// 判断mid是在左段还是右段
if (nums[mid] >= nums[left]) {
// 判断target是在mid的左边还是右边,来调整left和right
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
// 同上,根据情况调整left和right
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {4, 5, 6, 7, 0, 1, 2};
int target = 0;
int result = solution.search(nums, target);
System.out.println("The target " + target + "'s index is: " + result);
}
}
这段 Java 代码实现了题目要求的功能,采用了二分查找算法来处理旋转排序数组中的搜索问题。代码首先确定搜索的左右边界,然后在循环中不断地将搜索范围缩小,直到找到目标值或者搜索范围为空。
Rust 版本
fn search(nums: Vec<i32>, target: i32) -> i32 { let (mut left, mut right) = (0, nums.len() as i32 - 1); while left <= right { let mid = left + (right - left) / 2; if nums[mid as usize] == target { return mid; } if nums[left as usize] <= nums[mid as usize] { if target >= nums[left as usize] && target < nums[mid as usize] { right = mid - 1; } else { left = mid + 1; } } else { if target > nums[mid as usize] && target <= nums[right as usize] { left = mid + 1; } else { right = mid - 1; } } } -1 } fn main() { let nums = vec![4, 5, 6, 7, 0, 1, 2]; let target = 0; let result = search(nums, target); println!("The target {}'s index is: {}", target, result); }
这段 Rust 代码实现了题目要求的功能,使用二分查找算法来处理旋转排序数组中的搜索问题。代码通过不断调整搜索的左右边界来缩小搜索范围,直到找到目标值或者确定目标值不在数组中。
总结
上述解法采用了二分查找算法来解决在一个部分旋转的有序数组中查找特定目标值的问题。算法的核心思想是利用数组的部分有序性来判断目标值可能位于的区间,并相应地调整搜索范围。
具体步骤如下:
- 初始化两个指针
left
和right
,分别指向数组的起始位置和结束位置。 - 进入一个循环,在
left
小于等于right
的条件下执行。 - 计算中间位置
mid
,并检查nums[mid]
是否等于target
。如果等于,直接返回mid
作为找到的索引。 - 如果
nums[mid]
不等于target
,则判断mid
是在旋转数组的左段还是右段。- 如果
nums[mid]
大于等于nums[left]
,则mid
左侧是有序的。- 如果
target
在nums[left]
和nums[mid]
之间,调整right
为mid - 1
。 - 否则,调整
left
为mid + 1
。
- 如果
- 如果
nums[mid]
小于nums[left]
,则mid
右侧是有序的。- 如果
target
在nums[mid]
和nums[right]
之间,调整left
为mid + 1
。 - 否则,调整
right
为mid - 1
。
- 如果
- 如果
- 如果循环结束后没有找到
target
,返回-1
表示target
不在数组中。
这种方法的时间复杂度为O(log n)
,因为每次迭代都会将搜索范围减半,这是二分查找的特性。这种算法适用于复杂度要求较高的场景,能够有效地处理旋转数组的搜索问题。