【算法学习】剑指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;    }

说明:以上代码,只经过简单测试,不保证完全正确,非常欢迎评论区举出反例。

0 0
原创粉丝点击