Rotated sorted array 中的搜索问题
Rotated sorted array (RSA)是一个比较特殊但是其实大家也很熟悉的数据结构,从名字我们就能看到RSA就是从普通的排序数组sorted array中演变而成的。
让我们看一个例子:
Sorted Array: 1234567
Rotated Sorted Array 5671234
其实就是将普通的排序数组进行了头尾相连的转动。
今天我们在这篇文章中将会讲到3种在Rotated sorted array中的搜索问题。
Find minimum in rotated sorted array
这道题看到名字就知道是要我们寻找到在RSA中的最小值的下标。如果没有接触过的同学一开始肯定会想对这个数组进行findmin的方法,用两个变量同时存储最小值和最小值的下标,这样遍历一遍数组就可以得到我们需要的值,此种方法的时间复杂度是
O
(
n
)
O(n)
O(n) 看起来已经很Ok了,但是能不能更快呢?
肯定是可以的,当我们看到sorted array的时候就应该最先思考一下是否可以使用二分搜索来解决这个问题。但是这道题并没有给出我们的目标数,我们应该怎么样确定自己的目标数呢?
让我们以上面的例子把RSA 拆成两个部分: 567 1234
明显前半部分的元素都是大于后半部分的,这样我们就可以确定了目标数其实是小于数组内最后一个元素的第一个元素。
可能很多同学也会迷惑为什么不是小于数组的第一个数?小于数组的第一个数看起来也是对的
但是我们必须考虑到一个特殊的case那就是这个rotated sorted array其实没有被rotated过,那这样搜索就会失败。
让我们看看java的实现吧!
class FindMin{
public int findMin(int[] nums){
if(nums == null || nums.length == 0){
return -1;
}
int start = 0, end = nums.length -1;
int target = nums[end];
while(start + 1 < end){
int mid = (end-start)/2 + start;
if(nums[mid] <= target){
end = mid;
}else{
start = mid;
}
}
// 此处可以返回下标或者是最小的元素。
return Math.min(nums[start],nums[end]);
}
}
在RSA 中对于特定数字的查找
这一题相较于之前的题目出现了一些变化,给定了我们一个需要查找的目标数,同样也也是需要在$O(logn)的时间内完成查找,一样我们还是用二分法来解决这个问题。
思路有两种,第一种是先利用上一题的findmin的方式先找到RSA中的pivot将数组划分成为两个独立的数组,然后再进行二分搜索,但是这种方法写起来代码量比较大,思路也比较简单,这里我们就不实现这种方式了。我们换一个稍微elegant一点的实现方式。让我们看看我们现在手头有一些什么数据可以帮我们定位我们的target。 根据二分法的基本要求我们都会拥有 start, end, mid 这三个数据,再根据我们需要查找的target 就可以将数组划分成为没有四种情况,然后就再根据这四种情况来来选择二分法应该舍去哪一边也就是我们的start和end如何移动。
- nums[mid] >= nums[start]
- target <= nums[mid] && target >= nums[start]
- target > nums[mid] || target < nums[start]
- nums[mid] < nums[start]
- target >= nums[mid] && target <= nums[end]
- target < nums[mid] || target > nums[end]
让我们来看看这四种情况: 依然我们以上面的例子将 RSA 分为两个部分 Part1 567, Part2 1234
1 的情况发生在mid处在part1中. 这时候我们用target对比mid和start的值,可以将数组分为两个没有交际的部分。
2 的情况发生在mid处于part2中,我们一样是利用target对比mid和end的值,也可以将数组分为两个没有交际的部分。
这样我们就可以利用这两个情况来判断到我们的start和end指针的移动,从而构成了一个完整的二分搜索。
让我们看看这个算法的java实现吧:
class Search {
public int searchInRSA(int[] nums, int target){
if(nums == null || nums.length == 0){
return -1;
}
int start = 0, end = nums.length - 1;
while(start + 1 < end){
int mid = (end-start)/2 + start;
if(nums[mid] >= nums[start]){
if(target >= nums[start] && target <= nums[mid]){
end = mid;
}else{
start = mid;
}
}else{
if(target >= nums[mid] && target <= nums[end]){
start = mid;
}else{
end = mid;
}
}
}
if(nums[start] == target) return start;
if(nums[end] == target) return end;
return -1;
}
}
这样我们就可以很好的解决在RSA中寻找某一个特定元素的问题了。
Find Minimum in Rotated Sorted Array II
之前我们所处理的情况都是当数组中没有重复元素的情况,当数组中允许出现重复的情况我们应该如何处理呢?
其实我们依然可以使用二分法,只是在面对相等的情况的时候需要做出调整
如果mid < nums[end] 的情况时,向前移动end,如果mid > nums[end]的时候向后移动start。如果出现其他的情况则只是把end指针向前移动一个元素这样就可以保证我们最后依然可以找到RSA中的最小值。
class findMin2{
public int findMin(int[] nums) {
if(nums == null || nums.length == 0){
return -1;
}
int start = 0;
int end = nums.length-1;
while(start + 1 < end){
int mid = (end-start)/2 + start;
if(nums[mid] < nums[end]){
end = mid;
}else if(nums[mid]>nums[end]){
start = mid;
}else{
end--;
}
}
return Math.min(nums[start],nums[end]);
}
}
这样我们就可以在一个可能有重复元素的情况下依然找到RSA中的最小值
Search in Rotated Sorted Array II
那我们如何在一个拥有重复元素的RSA中寻找特定的元素呢?其实思想也是一样的,当我们遇到和start或者时end一样的mid的时候,只是单独向前或者是向后移动一个元素即可,让我们看看java的实现
class Search2{
public boolean search(int[] nums, int target) {
if(nums == null ||nums.length == 0){
return false;
}
int start = 0;
int end = nums.length-1;
while(start + 1 < end){
int mid = (end-start)/2 + start;
if(nums[mid] == target){
return true;
}
if(nums[start] == nums[mid]){
start++;
continue;
}
if(nums[mid] > nums[start]){
if(target < nums[mid] && target>= nums[start]){
end = mid;
}else{
start = mid;
}
}else{
if(target > nums[mid] && target <= nums[end]){
start = mid;
}else{
end = mid;
}
}
}
if(nums[start] == target){
return true;
}
if(nums[end] == target){
return true;
}
return false;
}
}