leetcode中的旋转排序数组问题:

关于旋转排序数组leetcode中共有4道题目,思路都是基于二分查找。

什么是旋转排序数组?

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

具体问题

找最小值和普通搜索两种

找最小值问题

1.假设数组中不存在相同元素(153题 中等)
示例:
输入: [3,4,5,1,2]
输出: 1
思路:

每次将mid和hi位置的数比较即可判断最小值所在的区间

代码:
 public int findMin(int[] nums) {
        int l=0,h=nums.length-1;
        while (l<h){
            int mid=l+(h-l)/2;
            if (nums[mid]>nums[h]){
                l=mid+1;
            }else {
                h=mid;
            }
        }
        return nums[l];
    }
2.假设数组中存在相同元素(154题 困难)
示例:
输入: [1,1,1,0,1]
输出: 0
思路:

如果数组元素允许重复的话,那么就会出现一个特殊的情况:nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1},l、m 和 h 指向的数都为 1,此时无法知道最小数字 0 在哪个区间。

代码:
public int findMin(int[] nums) {
        int l=0,h=nums.length-1;
        while (l<h){
            int mid=l+(h-l)/2;
            if (nums[l]==nums[mid]&&nums[mid]==nums[h]){
                return find(nums,l,h);//切换到顺序查找
            }
            else if (nums[mid]<=nums[h]){
                h=mid;
            }else {
                l=mid+1;
            }
        }
        return nums[l];
    }
    public int find(int[] nums,int l,int h){
        for (int i = l; i <h ; i++) {
            if (nums[i]>nums[i+1]){
                return nums[i+1];
            }
        }
        return nums[l];
    }

搜索问题

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

1.假设数组中不存在相同元素(33题 中等)
实例:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
思路:

可以基于153题,第一次二分查找找到最小值的位置(翻转点的位置)。有了最小值的索引便有个一个映射关系,mid和realmid。第二遍的二分查找就是一个常规的二分查找,但要考虑映射关系,即每次拿realmid的值比较而不是mid位置的值。

代码:
public int search(int[] nums, int target) {
        int l=0,h=nums.length-1;
        //用二分查找找到最小值的索引
        while (l<h){
            int mid=l+(h-l)/2;
            if (nums[mid]<=nums[h]){
                h=mid;
            }else {
                l=mid+1;
            }
        }//l=h 为最小值的索引 也就是翻转的位置

        int rot=l;
        l=0;
        h=nums.length-1;
        //第二遍二分查找 就是正常的二分查找但要考虑翻转点
        while (l<=h){
            int mid=l+(h-l)/2;
            int realmid=(mid+rot)%nums.length; //通过映射关系找到真实的中间点
            if (nums[realmid]==target){
                return realmid;
            }else if (nums[realmid]>target){
                h=mid-1;
            }else {
                l=mid+1;
            }
        }
        return -1;
    }
2.假设数组中存在相同元素(81题 中等)
示例:
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true
思路:

这题相当于在154题的基础上,
第一步,用154题的函数找到存在重复元素数组中最小值(翻转点)的位置;
第二步,参考1的做法,通过映射关系完成一次常规二分查找。

代码:
 public boolean search(int[] nums, int target) {
        
        //调用函数找到最小值的索引
        int rot=findMinIndex(nums);

        int l=0,h=nums.length-1;
        
        //二分查找 就是正常的二分查找但要考虑翻转点
        while (l<=h){
            int mid=l+(h-l)/2;
            int realmid=(mid+rot)%nums.length; //通过映射关系找到真实的中间点
            if (nums[realmid]==target){
                return true;
            }else if (nums[realmid]>target){
                h=mid-1;
            }else {
                l=mid+1;
            }
        }
        return false;
    }
    public int find(int[] nums,int l,int h){ //顺序查找
        for (int i = l; i < h; i++) {
            if (nums[i]>nums[i+1]){
                return i+1;
            }
        }
        return l;

    }
    public int findMinIndex(int[] nums) {
        int l=0,h=nums.length-1;
        while (l<h){
            int mid=l+(h-l)/2;
            if (nums[l]==nums[mid]&&nums[mid]==nums[h]){
                return find(nums,l,h);//切换到顺序查找
            }
            else if (nums[mid]<=nums[h]){
                h=mid;
            }else {
                l=mid+1;
            }
        }
        return l;
    }

总结:

旋转排序数组中的问题无非就是查找问题,而排序数组中的查找问题采用二分查找效率最高,所以要尽量利用其中的顺序关系转化为二分查找来解决。

对于找最小值(旋转点)问题,又可以分为无重复元素和有重复元素;对于无重复元素找最小值问题,每次将mid位置元素和h位置元素比较;对于有重复,注意特例类似[1,1,1,0,1,1],出现特例时就要局部利用顺序查找来继续查找了,所以复杂度会由O(logN)退化到O(N)。

对于搜索问题,第一步都是解决找旋转点的问题,根据有无重复元素采用不同函数。第二步,在找到旋转点后,便有了“mid”和“realmid”的映射关系,就可以转化为普通二分查找搜索问题。

猜你喜欢

转载自www.cnblogs.com/10zhang/p/9904992.html