前言
最近看到有关数组右移的面试题,感觉有点意思,就和大家分享一下吧!
题目
将一个长度为size的数组A内的元素循环右移n位(当然左移也可以),比如数组 {1, 2, 3, 4, 5}右移3位之后就变成{3, 4, 5, 1, 2}。
注:这道题其实难度不大,接下来和大家分享一下三种方法吧:
解题详解
第一种方法
也是最常见的,也是最容易想到的:
想法
用一个一样大小的格外空间,然后遍历目标数组,令B [(i + n) % size] = A [i]
实现:
public int[] ShiftRight(int[] ints, int n)
    {
        int[] ints2 = new int[ints.length];
        for (int i=0; i < ints.length; i++)
        {
            ints2[(i+n)%ints.length] = ints[i];
        }
        return ints2;
    }
这个应该没有太大难度。
接下来就有点需要点脑细胞了
第二种方法
想法
我们可以观察到 {1, 2, 3, 4, 5} ->{3, 4, 5, 1, 2},这个过程中好像可以这样看
{1, 2, 3, 4, 5} ->{2, 1, 5, 4, 3}->{3, 4, 5, 1, 2}
怎么样,是不是有点头绪了,这里用到的是一个翻转的思想,右移3位,那我就可以
翻转前两位,再翻转后三位
实现
public static void Reverse(int m, int n, int[] ints) {
        for (int i = 0; i < (Math.abs(n - m) / 2) + 1; i++) {
            int temp = ints[n - 1 - i];
            ints[n - 1 - i] = ints[m - 1 + i];
            ints[m - 1 + i] = temp;
        }
    }
    public static int[] ShiftRight(int step, int[] ints) {
        int size = ints.length;
        int m = size - step;
        if (m <= 0) {
            return ints;
        }
        Reverse(m + 1, size, ints);
        Reverse(1, m, ints);
        Reverse(1, size, ints);
        return ints;
    }
这样的话,第二种也就解密了
这种较于第一种,优点是:
把格外空间缩减到了只是用一些辅助的基本数据类型
最重要的是,相比于第一种,这种可能更加会让你感受到思想的变化,以及算法的魅力~~
接下来就是第三种,也即是最后一种了。
第三种方法
想法
我们再观察下 {1, 2, 3, 4, 5} ->{3, 4, 5, 1, 2}
我们可以看到,我们可以这样思考,能不能把这个过程进行更细的拆分呢,就像:
因为右移3位,那么我们把数组下标为 0 的移到了下标为 3 的,这样我们就搞定了一个,然后再从下标为 3 的出发,右移它就要移到下标为 1 的位置,这样我们就解决了2个,如此下去,我们就能实现右移的工作,在这个过程中会有两个问题需要解决:
我们要这样几次,万一到了交换过的词后,还没全转换完怎么处理?
这时候,我列了两组数据进行对比思考下:
7 (数值长度为 7 的话) :
右移 1 需要1次
右移 2 需要1次
右移 3 需要1次
右移 4 需要1次
8 (数值长度为 7 的话) :
右移 1 需要1次
右移 2 需要1次
右移 3 需要1次
右移 4 需要4次
这看上去感觉就和公因数有点关系,而且还是最大公因数,那这个问题就解决了
这个过程我还修改了代码用工厂模式顺便实现了如果左移的话怎么处理
实现
interface ShiftFactory {
    GongYueShu gYS = new GongYueShu();
    ShiftValue sV = new ShiftValue();
    int[] shift(int[] ints, int n);
}
class ShiftLeft implements ShiftFactory {
    @Override
    public int[] shift(int[] ints, int n) {
        int GYS = gYS.gongYueShu(ints.length, -n);
        return sV.ShiftValue(ints, n, GYS);
    }
}
class ShiftRight implements ShiftFactory {
    @Override
    public int[] shift(int[] ints, int n) {
        int GYS = gYS.gongYueShu(ints.length, n);
        return sV.ShiftValue(ints, n, GYS);
    }
}
class ShiftValue {
    public int[] ShiftValue(int[] ints, int n, int GYS) {
        for (int i = 0; i < GYS; i++) {
            int index = ints[i];
            int help = 0;
            int Pn = (i + n + ints.length) % ints.length;
            while (Pn != i) {
                help = ints[Pn];
                ints[Pn] = index;
                index = help;
                Pn = (Pn + n + ints.length) % ints.length;
            }
            ints[i] = index;
        }
        return ints;
    }
}
class GongYueShu {
    public int gongYueShu(int a, int b) {
        int c;
        while (a % b != 0) {
            c = a % b;
            a = b;
            b = c;
        }
        return b;
    }
}
这里确定左移和右移需要注意的有以下两点:
1.GYS,也即是上面提到的轮询次数,这里我们要注意负数需取绝对值
2.操作到下一个目标位置的时候,需要 + ints.length,也即是数组长度,因为我们定义右移为正的话,那么左移就为负,防止越界。
好了,三种方法写完了,大家有什么看法吗?或者说觉得这里代码写得不好,也期待大家指出一起成长。
大家一起来思考一下,这个能使用在哪些方面呢?
评论