题目:
一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A0A1⋯AN−1)变换为(AN−M⋯AN−1A0A1⋯AN−M−1)(最后M个数循环移至最前面的M个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?
输入格式:
每个输入包含一个测试用例,第1行输入N(1≤N≤100)和M(≥0);第2行输入N个整数,之间用空格分隔。
输出格式:
在一行中输出循环右移M位以后的整数序列,之间用空格分隔,序列结尾不能有多余空格。
输入样例:
6 2
1 2 3 4 5 6
输出样例:
5 6 1 2 3 4
代码:
#include <stdio.h>
int main() {
int N, M; //N是输入元素的个数,M是右移的位数
scanf("%d%d", &N, &M);
int arr[N + 1];
for (int i = 1; i <= N; i++) {
scanf("%d", &arr[i]);
}
M %= N;
if (M == 0)
M = N;
int i;
i = N - M + 1;
int cnt = 0;
while (1) {
if (cnt != 0)
printf(" ");
printf("%d", arr[i]);
cnt++;
if (i == N)
i = 1;
else
i++;
if (i == N - M + 1)
break;
}
return 0;
}
分析:
上述代码没有涉及翻转数组元素。
本题给的样例分析
6 2
1 2 3 4 5 6
M代表移动的位数,当移动位数是2,则从最后一位往前数,倒一是“6”,倒二是“5”。
找到倒二位之后,从倒二位开始输出,当输出的元素是最后一位(示例为“6”)的时候,从头开始输出(示例为“1”),然后一直输出,一直输出,直到倒二的前一位,停止。
5 6 1 2 3 4
本篇文章代码分析
/* 代码一 */
int arr[N + 1];
for (int i = 1; i <= N; i++) {
scanf("%d", &arr[i]);
}
代码一中,定义了一个用来存储用户输入的数的数组,不同的是,这个数组没有用上arr[0],而是从下标为1开始直到下标为N+1(下图)。
/* 代码二 */
M %= N;
if (M == 0)
M = N;
int i; //i代表输出的第一个数在数组中的下标
i = N - M + 1;
代码二中,主要的作用在于,当我们输入的M(也就是右移的位数)大于等于N时,我们要再次从尾巴开始数,比如题目给的示例:6个数“ 1 2 3 4 5 6 ”。M为2时,从倒二“5”开始输出;M为5时,从倒五“2”开始输出;M为8时,数完一轮剩2,再次从倒一数,然后倒二“5”,最后从“5”开始输出。所以这就是为什么要取余的原因,“M = 8” % “N = 6” = “M = 2”。
但是!当M刚好是N或者是N的倍数或者是0时,取余之后M为0,这时候是要从第一位开始输出的。而N = 6, M = 0或6或12或18...,取余后“i = N - M + 1”这时候会等于7,我们知道应该是要从一开始,所以让M由0变成N,多减一次N就可以。(为什么“i = N - M + 1”中有加一?参考代码一解释。)
/* 代码三 */
while (1) {
if (cnt != 0)
printf(" ");
printf("%d", arr[i]);
cnt++;
if (i == N)
i = 1;
else
i++;
if (i == N - M + 1)
break;
}
代码三应该是比较好理解的。
while(1)表示一直循环,循环出口是当遍历完一遍之后,下个元素又是最开始的那个元素,就停止。
值得注意的是,是“i”先加,然后才判断下一个是不是最开始的那个元素(或者说是否已经遍历过了)。