问题:
本文以书上提到的四种解决办法用JAVA来实现
解决方法一:借助临时数组
基本思想
首先将向量s的前i个元素复制到临时数组中,然后将数组索引从i到n-1的元素向左移动i个位置,最后把临时数组中的值复制到s余下的i个位置。
代码实现
/**
* 借助临时数组来实现向量旋转
*
* @param s s向量
* @param i 向左旋转值
*/
private static void moveString1(char[] s, int i) {
int n = s.length;
// 1.将向量s的前i个元素复制到临时数组中
char[] a = new char[i];
for (int j = 0; j < i; j++) {
a[j] = s[j];
}
// 2.数组索引从i到n-1的元素向左移动i个位置
for (int x = 0, j = i; j < n; j++, x++) {
s[x] = s[j];
}
// 3.把临时数组中的值复制到s余下的i个位置
for (int x = 0, j = n - i; j < n; j++, x++) {
s[j] = a[x];
}
}
优缺点
执行速度还是蛮快的,但是存在多余i个空间的消耗。
解决方法二:整体移动法
基本思想
有点像排队,排在头的那个人买完票,走出队列,人群都往前走一步,之后发现少买一张,这时候已经出队伍了,他必须重新排队,就只有排在队伍的末尾。
代码实现
/**
* 整体移动法
*
* @param s s向量
* @param i 向左旋转值
*/
private static void moveString2(char[] s, int i) {
int n = s.length;
// 4. 循环执行i次
for (int x = 0; x < i; x++) {
// 1. 头部数据缓存到t
char t = s[0];
// 2. 后面的n - 1个数整体向左移一位
for (int j = 0; j < n - 1; j++) {
s[j] = s[j + 1];
}
// 3. t赋值给最末尾
s[n - 1] = t;
}
}
优缺点
比起第一种实现,不用过多占用空间,但是在时间上消耗比较大。
解决方法三:复杂的杂技算法
基本思想
比较复杂的实现方式,书上比喻为耍杂技:缓存s[0]到t,s[0]被s[i]替换,s[i]被s[i+i]替换,s[i+i]又被s[i+i+i]替换,直到需要被s[0]替换。(注意,索引值需对n取余,比如 i%n, 2i%n,循环替换)。这相当于一次置换。
如果次数小于i和n的最大公约数,继续从s[1]开始:缓存s[1]到t,s[1]被s[1+i]替换,s[i+1]又被s[i+1+i]替换,类推,一直到需要被s[1]替换,同样索引值需要对n取余。
如此循环,一共执行i和n的最大公约数次置换。
代码实现
/** 杂技算法
* @param s s向量
* @param i 向左旋转值
*/
private static void moveString3(char[] s, int i) {
// n为字符总长度
int n = s.length;
// 需要最大公约数次置换
int numbers = gcd(n, i);
// 执行n和i的最大公约数次置换
for (int j = 0; j < numbers; j++) {
// 记录每一次替换所需步数
int steps = 0;
// 1. 每次置换开始的数组索引缓存到t
char t = s[j];
// 2. 需要替换的数组游标
int index = j;
// 5. 需要从置换开始的数组索引取值时,循环结束
while ((index + i) % n != j) {
// 3. 被其后面的第i个值替换,需要对长度取余
s[index % n] = s[(index + i) % n];
// 4.游标后移i个位置
index += i;
steps++;
}
// 6. 需要从置换开始的数组索引取值时,将t赋值给它
s[index % n] = t;
System.out.println("第" + (j + 1) + "次置换,移动次数为:" + steps);
}
}
// 递归方式求最大公约数
private static int gcd(int a, int b) {
if (b != 0) {
return gcd(b, a % b);
}
return a;
}
优缺点
好复杂的算法,比前面两种实现复杂得多。我是在小本本上人工实现置换,刚开始不知道需要最大公约数次置换,以为是i次(用的是n = 12, i = 3,碰巧最大公约数是3),然后增大i值到5的时候,发现结果是错误的。就挨个数据更改,把i、n和置换次数总结了出来。
置换次数还是比较喜感的,最大公约数是1的时候,只需要置换一次。
至于为什么时置换最大公约数次,需要了解欧几里得算法。
解决方法四:数组分段求逆法
基本思想
把数组从i位置分成两段xy,x求逆,y求逆,然后xy整体求逆。
比如向量abc n=3,i=2
x = ab , y = c
x求逆= ba
y求逆= c
xy = bac
xy求逆 = cab
代码实现
int i = 3;
reverse(s, 0, i - 1);
reverse(s, i, s.length - 1);
reverse(s, 0, s.length - 1);
// 数组[i,j]区间的值反转
private static void reverse(char[] s, int i, int j) {
for (; i <= j; i++, j--) {
char temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
优缺点
果然是“啊哈,灵机一动”,这种算法相比前面的简单得多。
实际上公式就是:(ab)^r –> (a^r b^r)^r。