引入
这次遇到的题目如下:
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的值来判断:
- 如果target值正好是mid的值,直接返回mid即可。
- 如果left是小于等于mid的值的,那说明从left到mid是有序的,然后判断target是不是在它们之间,如果不在left=mid+1;如果在它们之间,那接下来的二分查找和普通排序数组的二分查找没有差别了。
- 如果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;
}
}