【LeetCode】有序旋转数组的查找(4)

  有序旋转数组是指将有序数组向左或者向右移动k个位置得到的结果,其查找算法不难理解,因为局部有序,因此很容易想到二分查找是最合适的方法,时间复杂度O(nlogn),本文总结四道相关的算法题目。

(一)旋转数组

题目:189. 旋转数组

题目描述:

  给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:

	输入: [1,2,3,4,5,6,7] 和 k = 3
	输出: [5,6,7,1,2,3,4]

	解释:
	向右旋转 1 步: [7,1,2,3,4,5,6]
	向右旋转 2 步: [6,7,1,2,3,4,5]
	向右旋转 3 步: [5,6,7,1,2,3,4]

解题思路:三次反转

  实际上本题不难想到解答,也是在面试中很常见的一个题目,实际上它类似于【剑指Offer】43、左旋转字符串,对于左旋转,可以分为前k个元素和其他元素,而右旋转可以分为后k个元素和前面的其他元素,仍然可以借鉴三次反转的算法来实现。

代码思路:

class Solution {
    public void rotate(int[] nums, int k) {
        //右移k位,三次反转
        if(nums==null || nums.length==0 || k==0)
            return ;
        int len=nums.length;
        k=k%len;

        reverse(nums,len-k,len-1);
        reverse(nums,0,len-k-1);
        reverse(nums,0,len-1);
    }
    //数组反转
    public void reverse(int[] nums,int begin,int end){
        int i=begin,j=end;
        while(i<j){
            int temp=nums[i];
            nums[i]=nums[j];
            nums[j]=temp;
            i++;
            j--;
        }
    }
}

(二)有序数组的查找

  在有序数组中查找一个特定的值target,这就是典型的二分查找算法,其过程为:

  • 若 target == nums[mid],直接返回
  • 若 target < nums[mid],则 target 位于左侧区间 [left,mid) 中。令 right = mid-1,在左侧区间查找
  • 若 target > nums[mid],则 target 位于右侧区间 (mid,right] 中。令 left = mid+1,在右侧区间查找
    public boolean search(int[] nums, int target) {
        if(nums==null || nums.length==0)
            return false;
        int low=0,high=nums.length-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if(nums[mid]==target)
                return true;
            else if(target<nums[mid]){ 
                high=mid-1;
            }else{
                low=mid+1;
            }
        }
        return false;
    }

(三)搜索旋转排序数组

题目:33. 搜索旋转排序数组

题目描述:

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

  搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。你的算法时间复杂度必须是 O(log n) 级别。

示例:

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

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

解题思路:二分查找

  对于本题,二分查找是自然而然可以想到的思路,特别是更要求算法时间复杂度是O(logn),那几乎二分查找就成为了唯一的选择。但是,由于本题并不是完全有序,而是经过旋转后的局部有序

  因此,对于这种局部有序的数组,我们可以观察到一个特点:从任一位置进行分隔,则必有其中一半是有序的。因此,我们可以仍然从中点位置进行分隔,然后通过将最左边元素nums[low]和nums[mid]进行比较,从而可以判断出来,是左侧还是右侧是连续有序的,进而可以选择接下来继续选择哪一边进行继续查找。

1、若 target == nums[mid],直接返回

2、若 nums[left] <= nums[mid],说明左侧区间 [left,mid]「连续递增」。此时:

  若 nums[left] <= target <= nums[mid],说明 target 位于左侧。令 right = mid-1,在左侧区间查找

  否则,令 left = mid+1,在右侧区间查找

3、否则,说明右侧区间 [mid,right]「连续递增」。

  此时:
若 nums[mid] <= target <= nums[right],说明 target 位于右侧区间。令 left = mid+1,在右侧区间查找

  否则,令 right = mid-1,在左侧区间查找

代码实现:

class Solution {
    public int search(int[] nums, int target) {
        if(nums==null || nums.length==0)
            return -1;
        int low=0,high=nums.length-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if(nums[mid]==target)
                return mid;
            else if(nums[low]<=nums[mid]){  //左侧连续
                if(target<nums[mid] && target>=nums[low])
                    high=mid-1;
                else
                    low=mid+1;
            }else{      //右侧连续
                if(target>nums[mid] && target<=nums[high])
                    low=mid+1;
                else
                    high=mid-1;
            }
        }
        return -1;
    }
}

(四)搜索旋转排序数组II

题目:81. 搜索旋转排序数组 II

题目描述:

  假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

示例 :

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

解题思路:

  本题是上一题的延续,其区别在于数组中是否包含重复元素,当数组中允许元素重复时,如果nums[low]和nums[mid]是相等的,那我们就无法判断究竟是哪一边连续有序,这时我们可以让指针只移动一位,相当于排除一个元素。其余和上题基本相同。

代码实现:

class Solution {
    public boolean search(int[] nums, int target) {
        if(nums==null || nums.length==0)
            return false;
        
        int low=0,high=nums.length-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if(nums[mid]==target)
                return true;
            if(nums[low]==nums[mid]){
                low++;
            }else if(nums[low]<nums[mid]){  //左边有序
                if(target>=nums[low] && target<nums[mid])
                    high=mid-1;
                else
                    low=mid+1;
            }else{ //右边有序
                if(target>nums[mid] && target<=nums[high])
                    low=mid+1;
                else
                    high=mid-1;
            }
        }
        return false;
    }
}

总结:

  本文总结了关于旋转排序数组的四道题目,重点是二分查找算法的变形应用,是一个重要的算法知识点。

原文地址:https://www.cnblogs.com/gzshan/p/12570332.html