搜索旋转排序数组

搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

搜索旋转数组中最小的数

在寻求上面题目的解时,我们先简化一下题目,看一下这一道。

这道题最直观的做法并不难,从头到尾遍历数组一次,我们就能找出最小的元素。这种思路的时间复杂度显然是O(n)。但是这种思路没有利用输入的旋转数组的特性。

我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素。还注意到最小的元素刚好是这两个子数组的分界线。在排序的数组中我们可以用二分查找法实现O(logn)的查找。本题给出的数组在一定程度上是排序的,因此哦们可以试着用二分查找法的思路来寻找这个最小的元素。

和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。按照题目中旋转的规则,第一个元素应该是大于或者等于最后一个元素的(这其实不完全对,还有特例)。

接着我们可以找到数组中间的元素。如果该中间的元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第一个指针指向该中间元素,这样就可以缩小寻找的范围。移动之后的第一个指针仍然位于前面的递增子数组。

同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时该数组中最小的元素应该位于该中间元素的前面。我们可以把第二个指针指向该中间元素,这样也可以缩小寻找的范围。移动之后的第二个指针仍然位于后面的递增子数组。

按照上面的思路,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。也就是它们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件。

前面我们提到,在旋转数组中,由于是把递增排序数组前面的若干个数字搬到数组的后面,因此第一个数字总是大于或者等于最后一个数字。但按照定义还有一个特例:如果把排序数组的前面的0个元素搬到最后面,即排序数组本身,这仍然是数组的一个旋转,我们的代码需要支持这种情况。此时,数组中的第一个数字就是最小的数字,可以直接返回。

我们再来看一个例子。数组{1,0,1,1,1}和数组{1,1,1,0,1}都可以看成递增排序数组{0,1,1,1,1}的旋转。

在这两种情况中,第一个指针和第二个指针指向的数字都是1,并且两个指针中间的数字也是1,这三个数字相同。在第一种情况中,中间数字位于后面的子数组;在第二种情况中,中间数字位于前面的子数组。因此,当两个指针指向的数字及它们中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的子数组还是后面的子数组,也就无法移动两个指针来缩小查找的范围。此时我们需要顺序查找。

public int searchMin(int[] nums){
        // 搜索旋转矩阵中最小的
        if (nums.length == 0){
           return 0;
        }
        int low = 0;
        int high = nums.length - 1;
        int mid = 0;
        while (nums[low] >= nums[high]){
            if (high - low == 1){
                mid = high;
                break;
            }
            mid = (low+high) / 2;
            if (nums[low]== nums[high] && nums[mid] == nums[low]){
                return minInOrder(nums,low,high);
            }
            if (nums[mid] > nums[low]){
                low = mid;
            } else if (nums[mid] < nums[high]){
                high = mid;
            }
        }
        return nums[mid];

    }
    private int minInOrder(int[] numbers,int index1,int index2){
        int result = numbers[index1];
        for(int i = index1+1;i<=index2;++i){
            if (result > numbers[i]){
                result = numbers[i];
            }
        }
        return result;
    }

搜索旋转排序数组

下面我们回到这个问题上,如何搜索旋转排序数组。这道题最直观的做法并不难,从头到尾遍历数组一次,我们就能找出目标的元素。这种思路的时间复杂度显然是O(n)。但是这种思路没有利用输入的旋转数组的特性。

同样的,我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素。在排序的数组中我们可以用二分查找法实现O(logn)的查找。本题给出的数组在一定程度上是排序的,因此可以试着用二分查找法的思路来寻找这个目标的元素。

和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。按照题目中旋转的规则,第一个元素应该是大于或者等于最后一个元素的(这其实不完全对,还有特例)。

对于数组nums = [4,5,6,7,0,1,2], target = 1,我们先找到这个数组中最小的元素的下标,也就是找到这个数组的分界点,接着判断target是属于数组的左半部分还是右半部分。接着直接使用二分查找就可以完成任务。

class Solution {
    public int search(int[] nums, int target) {
        if (nums.length == 0){
            return -1;
        }
        if (nums.length == 1){
            if (nums[0] == target){
                return 0;
            } else{
                return -1;
            }
        }

        int mid = searchMinIndex(nums); // 旋转的分界点
        if (mid == 0){
            return searchTwo(nums,target,0,nums.length-1);
        }
        if (target < nums[mid]){
            return -1;
        } else if (target>=nums[mid] && target<=nums[nums.length-1]){
            return searchTwo(nums,target,mid,nums.length-1);
        } else if (target>=nums[0] && target<=nums[mid-1]){
            return searchTwo(nums,target,0,mid-1);
        } else{
            return -1;
        }
    }
    private int searchTwo(int[] nums,int target,int lo,int hi) {

        while (lo <= hi){
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] > target){
                hi = mid-1;
            } else if (nums[mid] < target){
                lo = mid+1;
            } else{
                return mid;
            }
        }
        return -1;

    }

    public int searchMin(int[] nums){
        // 搜索旋转矩阵中最小的
        int low = 0;
        int high = nums.length - 1;
        int mid = 0;
        while (nums[low] >= nums[high]){
            if (high - low == 1){
                mid = high;
                break;
            }
            mid = (low+high) / 2;
            if (nums[low]== nums[high] && nums[mid] == nums[low]){
                return minInOrder(nums,low,high);
            }
            if (nums[mid] > nums[low]){
                low = mid;
            } else if (nums[mid] < nums[high]){
                high = mid;
            }
        }
        return nums[mid];
    }
    public int searchMinIndex(int[] nums){
        int low = 0;
        int high = nums.length - 1;
        int mid = 0;
        while (nums[low] >= nums[high]){
            if (high - low == 1){
                mid = high;
                break;
            }
            mid = (low+high) / 2;
            if (nums[low]== nums[high] && nums[mid] == nums[low]){
                return minInOrder(nums,low,high);
            }
            if (nums[mid] > nums[low]){
                low = mid;
            } else if (nums[mid] < nums[high]){
                high = mid;
            }
        }
        return mid;

    }
    private int minInOrderIndex(int[] numbers,int index1,int index2){
        int result = numbers[index1];
        int i = index1+1;
        for(;i<=index2;++i){
            if (result > numbers[i]){
                result = numbers[i];
            }
        }
        return i;

    }
    private int minInOrder(int[] numbers,int index1,int index2){
        int result = numbers[index1];
        for(int i = index1+1;i<=index2;++i){
            if (result > numbers[i]){
                result = numbers[i];
            }
        }
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值