1. 前言

本文的一些图片, 资料 截取自编程之美

2. 问题描述

2.17 数组循环移位_编程之美

3. 问题分析

对于 这个问题, 书中给出了两种思路
解法一 : 将移动的位数模上数组的长度, 获取最后需要移动的位数[得到移动位数k], [因为移动的位数, 可能大于数组的长度], 然后在循环k次, 每次向右移动一位

解法二 : 相对于第一种解法来说, 巧妙的多, 模上数组的长度, 计算出真是需要移动的位数[得到移动位数k], 然后逆转[0, k), [k, arr.length), 最后在逆转[0, k), 最终结果即为所求

其他思路 : 我这里还搜集了一些其他的思路, 但是并没有遵循题目中的只是用两个临时变量的限制
    解法一 : 使用一个临时布尔数组, 来存放当前索引对应的元素是否被移动过, 构造两层循环, 外层循环 用于遍历数组, 内层循环用于从当前元素开始移动元素, 然后令当前元素为移动到的下一个元素的索引, 直到当前元素索引不为最初的当前元素
    解法二 : 因为可以通过计算, 计算出外循环的次数, 然后按照上面的思路来移动元素

4. 代码

/**
 * file name : Test25ArrayRightShift.java
 * created at : 8:53:50 AM May 25, 2015
 * created by 
 */

package com.hx.test03;

public class Test25ArrayRightShift {

    // 将数组  向右移动k位
    public static void main(String []args) {

        int[] intArr = {-50, 0, -14, -15, 32, -34, 19, 32, -25, -50, 41, -6, 45, 26, -38, 36, -4, -13, -8, -36 };
        int[] arrCopy = Tools.copyOf(intArr);

            int k = 8;

            Log.logWithoutPosition(intArr);
//          arrayRightShift01(intArr, k);
//          Log.log(intArr);

//          arrayRightShift02(intArr, k);
//          Log.log(intArr);

//          arrayRightShift03(intArr, k);
//          Log.log(intArr);

//          arrayRightShift0302(intArr, k);
//          Log.logWithoutPosition(intArr);

            arrayRightShift04(arrCopy, k);
            Log.logWithoutPosition(arrCopy);

            Log.log(Arrays.equals(intArr, arrCopy));

    }

    // 最简单的思路   时间复杂度为 O(n * k)
    public static void arrayRightShift01(int[] intArr, int k) {
        for(int i=0; i<k; i++) {
            arrayRightShiftOnce(intArr);
        }
    }

    // 先让k对intArr.length进行取模  然后再右移
    public static void arrayRightShift02(int[] intArr, int k) {
        k %= intArr.length;
        arrayRightShift01(intArr, k);
    }

    // 保留一个boolean[] 记录该位置是否移动
    // 然后遍历intArr  如果该位置没有移动, 那么保存下该位置的目标位置的值, 然后将该位置的值移动过去, 修改标志位, 令idx为目标位置的索引  继续循环, 直到idx所在的位置被移动过
    // 一次内循环会更新所有的(idx + k) % intArr.length的值 
    // 外循环 则向前推进  进而更新所有的值
    public static void arrayRightShift03(int[] intArr, int k) {
        k %= intArr.length;
        if(k == 0) {
            return ;
        }

        // 这里应该可以不用记录  是否移动的boolean[], 而while循环中的判定, 可以改为idx不为i         --2015.06.11
        boolean[] isMoved = new boolean[intArr.length];
        for(int i=0; i<intArr.length && (!isMoved[i]); i++) {
            int idx = i, target = -1, tmp = -1, tmp02 = -1;
            tmp = intArr[idx];
            while (!isMoved[idx]) {
                target = (idx + k) % intArr.length;
                tmp02 = intArr[target];
                intArr[target] = tmp;
                tmp = tmp02;
                isMoved[idx] = true;
                idx = target;
            }
        }

    }

    // 思路 和上面基本一致, 不过这里去掉了记录是否移动的数组
    public static void arrayRightShift0302(int[] intArr, int k) {
        k %= intArr.length;
        if(k == 0) {
            return ;
        }

        // 这里应该可以不用记录  是否移动的boolean[], 而while循环中的判定, 可以改为idx不为i         --2015.06.11
        // 需要循环的次数为intArr.length 和k的最大公约数
            // 如果length, 和k的最大公约数为1, 则在下面的过程中, i必然走过了intArr中所有的元素
            // 如果最大公约数为2    则在下面的过程中, i必然走过了intArr中所有的偶数索引元素                --2015.06.11
            // ...
        // i=0, ((i+k) % intArr.length) == i
        int loopNum = Tools.gcd(intArr.length, k);

        // 先移动第一个元素, 然后在循环移动, 直到移动到第i个元素
        for(int i=0; i<loopNum; i++) {
            int idx = i, target = -1, tmp = intArr[idx], tmp02 = -1;
            target = (idx + k) % intArr.length;
            tmp02 = intArr[target];
            intArr[target] = tmp;
            tmp = tmp02;
            idx = target;

            while (i != idx) {
                target = (idx + k) % intArr.length;
                tmp02 = intArr[target];
                intArr[target] = tmp;
                tmp = tmp02;
                idx = target;
            }
        }

    }

    // 思路 : 先逆转(0, mid)和 (mid, intArr.length)范围内的数字   在将整个数组 逆转
    // 非常的巧妙啊    即维护了数据的顺序[所有的数据都进行了两次reverse就维护了顺序]   有改变里数据的位置
    public static void arrayRightShift04(int[] intArr, int k) {
        int mid = intArr.length - k;
        reverse(intArr, 0, mid);
        reverse(intArr, mid, intArr.length);
        reverse(intArr, 0, intArr.length);
    }

    // 思路 : 先将intArr的最后一个元素保存起来, 然后在从intArr.length-2依次将该位置的元素复制到后面一个位置, 最后令intArr的最后一个元素为第一个元素[intArr[0]]
    // 从而实现  右移一位
    private static void arrayRightShiftOnce(int[] intArr) {
        int tmp = intArr[intArr.length-1];
        for(int i=intArr.length-2; i>=0; i--) {
            intArr[i+1] = intArr[i];
        }
        intArr[0] = tmp;
    }

    // 注意 : start, end包头不包尾   即包括start, 不包括end
    // 逆转 intArr中的[start - (end-1)]部分
    private static void reverse(int[] intArr, int start, int end) {
        int mid = (start + end) / 2;
        int endIdx = end - 1;
        for(int i=start; i<mid; i++) {
            int tmp = intArr[i];
            intArr[i] = intArr[endIdx];
            intArr[endIdx --] = tmp;
        }
    }

}

5. 运行结果

2.17 数组循环移位_数组_02

6. 总结

第一种解法虽然看起来是比较笨, 但是毕竟限制了只能使用两个元素, 第二个思路, 则是非常巧妙的思路

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!