Leetcode旋转数组——旋转与搜索

引入

这次遇到的题目如下

189.旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]

以及我将额外介绍旋转后的搜索方法

33.搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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

旋转数组题解

旋转数组有多种方法可以旋转。

首先,最暴力的方法就是旋转 k 次,每次将数组旋转 1 个元素。
也就是从[1,2,3,4,5]到[3,4,5,1,2]需要旋转3次,依次是:[5,1,2,3,4]、[4,5,1,2,3]、[3,4,5,1,2]。
也就是每次用一个单独的temp来保存放到数组首部的值,比如说第一次要保存5,然后从后往前依次让数组后移。其时间复杂度就是O(kN),空间复杂度是O(1)。
代码如下:

public class Solution {
    public void rotate(int[] nums, int k) {
    	k=k%nums.length;
        for (int i = 0; i < k; i++) {
            int temp = nums[nums.length - 1];
            for (int j = nums.length-1; j >0; j--) {
               	nums[j]=nums[j-1];
            }
            nums[0]=temp;
        }
    }
}

第二种方法就是使用额外的空间,来保存数组,太过于无脑,空间复杂度为O(N),时间复杂度为O(N),在此不做赘述。

第三种方法比较巧妙,是这样的。
当我们旋转数组 k 次, k%n个尾部元素会被移动到头部,剩下的元素会被向后移动。
在这个方法中,我们首先将所有元素反转。然后反转前 k 个元素,再反转后面 n-k个元素,就能得到想要的结果。

原始数组                  : 1 2 3 4 5 6 7
反转所有数字后             : 7 6 5 4 3 2 1
反转前 k 个数字后          : 5 6 7 4 3 2 1
反转后 n-k 个数字后        : 5 6 7 1 2 3 4 --> 结果

该方法代码如下:

public class Solution {
    public void rotate(int[] nums, int k) {
        k %= nums.length;
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }
    public void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }
}

这里暂时就介绍这么三种简单的方法。

搜索旋转数组题解

这里可以用到二分查找的方法,因为数组是部分有序的,可以通过mid和left和right的值来判断:

  1. 如果target值正好是mid的值,直接返回mid即可。
  2. 如果left是小于等于mid的值的,那说明从left到mid是有序的,然后判断target是不是在它们之间,如果不在left=mid+1;如果在它们之间,那接下来的二分查找和普通排序数组的二分查找没有差别了。
  3. 如果left是大于mid值的,那说明前半部分已经乱序了,也就是后半部分是有序的,我们在后半部分里比较target是不是在mid值和right值之间,如果在,接下来的二分查找也是一个普通的二分查找。如果不在,说明是在前半部分,right=mid-1。

我们发现,通过上面这种方法,判断的并不是乱序部分的值与target的比较,而是通过二分的方法,将乱序的数组分为两个数组,其中某个数组必然是不乱序的

class Solution {
    public int search(int[] nums, int target) {
        int L=0,R=nums.length-1;
        while(L<=R){
            int mid=R-(R-L)/2;
            if(nums[mid]==target){
                return mid;
            }
            if(nums[L]<=nums[mid]){
                //即前半部分有序
                if(target<nums[mid]&&target>=nums[L]){
                    //说明target在L和mid中间
                    R=mid-1;
                }else{
                    L=mid+1;
                }
            }else{
                //说明前半部分乱序
                if(target>nums[mid]&&target<=nums[R]){
                    //mid到最后的R处必然是有序的
                    L=mid+1;
                }else{
                    R=mid-1;
                }
            }
        }
        return -1;
    }
}
发布了364 篇原创文章 · 获赞 324 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/No_Game_No_Life_/article/details/103933341