【算法学习】剑指offer问题8——旋转数组
来源:互联网 发布:java web编程实战宝典 编辑:程序博客网 时间:2024/05/01 00:40
Java语言旋转数组相关问题:
一、数组旋转
将数组最开始若干元素移到数组的末尾,称为数组的旋转。
数组旋转有多种方法,这里介绍两种方法。
1、三次反转数组
public void rotate(int[] nums, int k) { int length = nums.length; k = k % length; reverse(nums, 0, length - 1); reverse(nums, 0, k - 1); reverse(nums, k, length - 1);}void reverse(int[] nums, int start, int end) { while(start < end) { int temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; }}
2、使用最大公约数
对于该方法,目前lz也只能证明其正确性,尚不知得出这种解法的思维过程。
假设数组长度为n,旋转步数为k。g为n和k的最大公约数,则任一个元素每次向右移动k位,共移动n/g次,则该元素回到原始位置,那么移动次数减一,则刚好移动到目标位置。
另外,最大公约数g将数组分成g部分,每部分的移动路径不交叉。
根据以上特性,实现代码如下:
public void rotate(int[] nums, int k) { int length = nums.length; k = k % length; int gcd = findGCD(length, k); int count = length / gcd; for(int i = 0; i < gcd; i++) { int index = i; for(int j = 0; j < count - 1; j++) { index = (index + k) % length; nums[i] ^= nums[index]; nums[index] ^= nums[i]; nums[i] ^= nums[index]; } } } int findGCD(int num1, int num2) { if(num1 == 0 || num2 == 0) { return num1 + num2; } else { return findGCD(num2, num1 % num2); } }
二、查找最小值
遍历一遍数组可以找到最小值,复杂度是O(n),但并没有用到旋转数组的特征。
因为旋转数组在一定程度上是排序的,因此考虑用二分查找的方法。
如图一所示,需要两个指针lo和hi分别指向数组的首尾,指针mid指向中间位置。
先考虑一般情况:
mid位置值和hi位置值相比,
1) 若mid位置值较大,则说明mid及之前位置的元素是有序且递增的,因此最小值应该在mid之后;
2) 若mid位置值较小,说明mid及之后的位置的元素是有序且递增的,因此最小值应该在mid及mid之前。
再考虑特殊情况,若数组中有相同元素,如图二:
mid位置的值和hi位置值相同,则分三种情况讨论:
1) 若lo位置值较hi位置值大,如图二中的a),则mid及之后的位置的元素是有序 且递增的,因此最小值应该在mid及mid之前。同一般情况下的2)。
2) 若lo位置值较hi位置值小,如图二中的b),则说明数组没有旋转,因此最小值应该在mid及mid之前。同一般情况下的2)。
3) 若lo位置值和hi位置值相等,如图二中的c)d)e),最小值可能在前半部分也可能在后半部分,此时无法使用二分查找。
根据以上分析,实现代码如下,不考虑特殊情况,时间复杂度为O(logn):
public int getMin(int[] nums) { if (nums == null) { throw new RuntimeException("The array is null."); } int lo = 0; int hi = nums.length - 1; if (nums[lo] < nums[hi]) { // 边界情况,表示数组并未被旋转,为提高效率,做特殊处理。 return nums[lo]; } while (lo < hi) { int mid = lo + (hi - lo) / 2; if (nums[mid] > nums[hi]) { // mid及之前有序,说明最小值在mid后面 lo = mid + 1; } else if (nums[mid] == nums[hi] && nums[lo] == nums[hi]) { // 特殊情形,无法判断最小值在前半段还是后半段,只能遍历数组。如:3333123,3123333 int min = nums[lo]; // 从lo到hi,遍历查找最小值 while (lo < hi) { if (min > nums[++lo]) { min = nums[lo]; } } return min; } else { // mid及之后有序,说明最小值在mid及前面 hi = mid; } } return nums[lo]; }
三、查找元素
在旋转数组中查找指定元素的位置,若数组中没有指定元素,则返回-1。
和查找最小值类似,使用二分查找的方式。主要步骤如下:
1) mid位置和hi位置比较,可以判断出前半部分或者后半部分有序;
2) 用指定元素和有序部分的首尾进行比较,判断指定元素是否在有序部分;
3)根据上一步的判断结果,选择下次循环数组的哪一部分。
实现代码如下:
public int getIndex(int[] nums, int target) { if (nums == null) { return -1; } int lo = 0; int hi = nums.length - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (nums[mid] == target) { return mid; } if (nums[mid] > nums[hi]) { // mid及之前有序 if (nums[mid] > target && nums[lo] <= target) { hi = mid - 1; } else { lo = mid + 1; } } else if (nums[mid] == nums[hi] && nums[lo] == nums[mid]) { // 特殊情况,无法判断,只能遍历如:3333123,3123333找2 for (int i = lo; i <= hi; i++) { if (nums[i] == target) { return i; } } return -1; } else { // mid及之后有序 if (nums[mid] < target && nums[hi] >= target) { lo = mid + 1; } else { hi = mid - 1; } } } return lo; }
说明:以上代码,只经过简单测试,不保证完全正确,非常欢迎评论区举出反例。
- 【算法学习】剑指offer问题8——旋转数组
- 剑指offer——旋转数组最小数字问题
- 剑指offer-旋转数组
- 剑指offer——旋转数组的最小数字
- 《剑指offer》——旋转数组的最小数字
- 剑指offer——旋转数组的最小值
- 剑指offer——旋转数组的最小数字
- 旋转数组的最小数字——剑指offer
- 剑指offer——旋转数组的最小数字
- 剑指offer——)旋转数组的最小数字
- 剑指offer——旋转数组的最小数字______
- 剑指offer——旋转数组的最小数字
- 剑指offer——6.旋转数组的最小数字
- 剑指offer——旋转数组的最小数字
- 剑指offer面试题8--数组旋转
- 剑指offer—旋转数组的最小数字
- 《剑指offer》—6、旋转数组的最小数字
- 剑指offer(6)—旋转数组的最小数字
- linux安装redis
- python json 转 dict , list
- kali linux启动服务
- 关于firefox(火狐)使用ajax无法发送请求
- 【Bug/Cplex】不明原因报错集合
- 【算法学习】剑指offer问题8——旋转数组
- 网易视频云邵峰:移动直播视频流分发网络的演进
- 【计算机视觉】Selective Search for Object Recognition论文阅读1
- souvenirs without help
- 基于Token的WEB后台认证机制
- linux下GraphicsMagick 的安装
- 通过appearance设置app主题
- Minimum Depth of Binary Tree
- 搭建自己的私有云