自测-3 数组元素循环右移问题 (20 分)

一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由 ( A ​ 0 A ​ 1 ⋯ A ​ N − 1 ) 变 换 为 ( A ​ N − M ​ ​ ⋯ A ​ N − 1 A 0 A 1 ⋯ A N − M − 1 ) (A_{​0} A_​{1}⋯A_{​N−1} )变换为(A_{​N−M}​​ ⋯A_{​N−1}A_{0}A_{1} ⋯A_{N−M−1}) A0A1AN1ANMAN1A0A1ANM1(最后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>
# include <stdlib.h>

int main() {
	int N,M,i = 0;
	scanf("%d %d",&N,&M);
	// 给指针分配指定大小的内存空间 
	int *p = (int *)malloc(sizeof(int) * N);
	// 指针间的整个操作和引用不一样 
	int *z = (int *)malloc(sizeof(int) * N);
	// 向指针指向的数组输入元素 
	for (i = 0;i < N;i++) {
		scanf("%d",&*p);
		// 指针指向下一个位置 
		p += 1;
	}
	// p指针指向数组的起始地址 
	p = p - N;
	// 开始交换数值
	/*注意:M比N大的情况*/
	M = M % N; 
	for(i = 0;i < N;i++) {
		// M + i小于N的时候
		
		if(M + i < N) {
			*(z + i + M) = *(p + i);
		} else{
			*(z + i + M - N) = *(p + i);
		}
	} 
	// 开始遍历输出
	for(i = 0;i < N - 1;i++) {
		printf("%d ",*(z + i));
	} 
	printf("%d",*(z + N - 1));
	return 0;
} 

提交截图:

在这里插入图片描述
感觉不该怎么写( ╯□╰ ),虽然使用的是两个指针,但是都分配了连续的内存空间的时候,本质上和数组并无区别!做这道题的收获只能说是熟悉了指针的用法吧/(ㄒoㄒ)/~~

题目的意思是说只能使用该数组本身,实现循环右移的效果
真正的代码如下:

#include<stdio.h>
int main()
{
    int m,n,i,a[10000];
    scanf("%d %d",&m,&n);
    for(i=0;i<m;i++)
        scanf("%d",&a[i]);
   n=n%m;
    int k=(m-n)%m;//注意取余的部分
    for(i=0;i<m-1;i++)
    {
        printf("%d ",a[k]);
        k=(k+1)%m;
    }
    printf("%d",a[k]);
    return 0;
}

我只能说想出答案的人真🐂,代码的意思是得出移位的数n和输入的元素个数m之后,其实从第一个元素a[m-n]开始,后面的数据可以看作是索引值依次加1,举个例子:6个数输入右移2位得到的正常输出是a[4]、a[5]、a[6]、a[7]、a[8]、a[9]只不过后面的超出了范围,因此使用取余操作,即变为a[4]、a[5]、a[0]、a[1]、a[2]、a[3]。说到这里,对想出答案的大佬的敬佩更浓了!!!

<think>嗯,我现在需要解决这个问题:将线性表循环右移指定的m位。用户要求用Java实现,并且要尽可能高效的时间和空间。首先,我得仔细理解题目要求。 题目中的例子是,比如原数组是1,2,5,7,3,4,6,8,右移3位后变成4,6,8,1,2,5,7,3。那这里的右移应该是每个元素右移动m位,超出的部从左边开始补上。例如,当m是3的时候,每个元素的位置i会变成(i + m) % n的位置?或者是不是应该反过来,比如循环右移m位相当于把最后m个元素移动到前面? 比如原数组长度为8,右移3位的话,那么最后3元素6,8,3会被移到前面?或者原数组是例子里的1,2,5,7,3,4,6,8,移动后的结果是4,6,8,...,原数组的最后三个元素是6,8,3吗?原例的输出是4,6,8作为前三个元素,所以可能我的理解有问题。原数组最后的三个元素是否是原数组中的最后三个?比如原数组的最后三个是6,8,但原数组长度是8?或者可能原题的例子有笔误? 或者原题中的例子可能应该是右移三位后的结果是将原数组中的后三个元素移到前面。比如原数组是(1,2,5,7,3,4,6,8),长度是8。右移3位的话,相当于将后三个元素6,8,?这里可能原题的例子可能有错误?或者可能我理解错了循环右移的定义? 或者另一种理解是,循环右移m位相当于每个元素右移动m位,超出末尾的元素从头部开始。比如,原数组是1,2,3,4,右移1位变成4,1,2,3。这样的话,每个元素i的位置变为 (i + m) mod n的位置?或者,循环右移m位等价于将数组的后m个元素移动到前面,剩下的元素后移。比如,当m=3时,数组的后三位被放到前面,剩下的元素依次排在后面。例如,原数组是1,2,3,4,5,右移3位的话,后三位是3,4,5,移动到前面,然后是1,2,结果3,4,5,1,2。这样,此时正确的做法应该是将数组为两部:前n-m个元素和后m个元素,然后将这两部交换顺序。例如,原数组成A和B两部,其中B的长度是m,然后合并B+A就是结果。但需要注意当m大于n的时候,应该取模,因为移动n位相当于没移动。所以,首先需要处理m的值,让m = m % n,如果m等于0的话,直接返回原数组即可。 例如,输入样例中的n=6,m=2,处理后m=2,然后成前n-m=4个元素和后2个元素。例如,原数组是1 2 3 4 5 6,后两位是5 6,移动到前面,然后加上前面的四个元素1 2 3 4,所以结果是5 6 1 2 3 4。这样是正确的。 所以,正确的做法是:首先计算m的有效移动次数,即m = m % n。如果m是0,直接返回原数组。否则,将数组的后m个元素移动到前面。 那如何高效地实现这个操作呢? 通常有三种方法: 1. 使用额外的数组空间,将后m个元素复制到新数组的前面,然后将前面的n-m个元素复制到后面。这种方法的时间复杂度是O(n),空间复杂度是O(n)。但是题目要求空间尽可能高效,所以这种方法可能不是最优的。 2. 三次反转法。例如,先反转整个数组,然后反转前m个元素,再反转剩下的n-m个元素。这样整体的时间复杂度是O(n),空间复杂度是O(1)。但需要实现反转函数。这种方法在数组原地操作,比较高效。但需要注意,三次反转的顺序是否正确? 或者,正确的步骤应该是:假设要循环右移m位,那么可以解为: - 反转整个数组:整个数组变成逆序。 - 反转前m个元素:这部变为原来的顺序的前m个元素? 或者可能需要不同的步骤。例如,原数组成两部,A和B,其中B是后m个元素循环右移后的结果是B+A。三次反转的方法是:先反转整个数组,这样B和A的顺序都反转了。然后反转前m个元素,得到原来的B顺序,再反转剩下的n-m个元素,得到原来的A顺序。这可能对吗? 举个例子,原数组是1 2 3 4 5 6,m=2。那么后两个元素是5 6。正确的右移后是5 6 1 2 3 4。 三次反转法: 第一次反转整个数组:6 5 4 3 2 1 然后反转前m=2个元素:5 6 4 3 2 1 然后反转剩下的n-m=4个元素(即从索引2到5):5 6 | 1 2 3 4。这样就得到了正确的结果。是的,这三次反转之后的结果是正确的。所以三次反转法是正确的。这种方法的时间复杂度是O(n),空间复杂度是O(1),非常高效。 那么这种方法适用于本题。那在Java中如何实现?需要写一个反转函数,接受数组,起始和结束索引,反转这部。 另一种方法是使用循环移动的方式,逐个元素移动,但时间复杂度会很高,比如O(n^2),对于n=1e5来说,这样的方法会超时。所以必须采用三次反转的方法。 所以,现在的问题是如何在Java中实现这个算法。此外,需要注意输入的m可能大于n,所以必须先将m取模n,得到有效的m。例如,当m=8,n=6时,m%n=2,所以移动2位即可。 那么,具体的步骤: 1. 处理输入的n和m。注意,当n=0时可能无法处理,但题目中n≥1。所以,当输入m之后,先计算m = m % n。如果m等于0,直接输出原数组即可。 2. 反转整个数组3. 反转前m个元素。 4. 反转剩下的n-m个元素。 这样就完成了循环右移m位的操作。例如,原来的数组是a1,a2,...,a_{n-m}, a_{n-m+1},..., an。反转整个数组得到an,...,a_{n-m+1}, a_{n-m}, ..., a1。然后反转前m个元素,得到a_{n-m+1},..., an, a_{n-m},...,a1。然后反转剩下的n-m个元素,得到a_{n-m+1},..., an, a1,..., a_{n-m}。这正好是循环右移m位后的结果。 所以,这个方法是正确的。 接下来,需要考虑Java中的实现。Java中的数组反转函数可以自己编写。例如,写一个reverse方法,参数是数组,起始位置和结束位置。例如,reverse(arr, 0, arr.length -1),反转整个数组。然后reverse(arr, 0, m-1),反转前m个元素。然后reverse(arr, m, arr.length -1),反转剩下的部。 例如,针对输入样例: 输入数组是1 2 3 4 5 6,n=6,m=2。处理后的m=2。反转整个数组得到6 5 4 3 2 1。然后反转前2个元素得到5 6 4 3 2 1。然后反转剩下的4个元素得到5 6 1 2 3 4。正确。 这样,三次反转后的结果正确。 那么,现在的问题是,如何在Java中高效实现这个反转函数。注意,输入的数组可能是很大的(1e5),所以需要尽可能高效的反转方法。反转的时间复杂度是O(k/2),其中k是反转的元素个数。对于整个数组来说是O(n/2),三次反转总的时间是O(3n/2),即O(n)。这符合时间要求。 另外,Java的数组处理需要注意索引是否正确。例如,当反转数组的start到end,包括这两个位置吗?是的,比如start=0,end=arr.length-1,此时反转整个数组。 所以,reverse函数的大致实现如下: public static void reverse(int[] arr, int start, int end) { while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } } 这样就能正确反转数组的一部。 接下来,整个算法的步骤: 1. 读取n和m,然后读取数组。 2. 如果n==0,或者m%n==0,直接输出原数组3. 否则,计算m = m % n。 4. 反转整个数组。 5. 反转前m个元素。 6. 反转剩下的n-m个元素。 然后输出数组即可。 现在,考虑如何读取输入。根据题目中的输入样例,输入的第一行是n和m,第二行是n个整数。在Java中,可以用Scanner或者BufferedReader来读取输入。但由于n可能很大(1e5),Scanner可能比较慢,所以最好使用BufferedReader。 例如: BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int n = Integer.parseInt(firstLine[0]); int m = Integer.parseInt(firstLine[1]); 然后读取第二行的数组: String[] parts = br.readLine().split(" "); int[] arr = new int[n]; for (int i =0; i <n; i++) { arr[i] = Integer.parseInt(parts[i]); } 然后处理m的值: if (n ==0) { // 题目中n≥1,所以可能不需要处理 } m = m % n; if (m ==0) { // 直接输出原数组 } else { reverse(arr, 0, n-1); reverse(arr, 0, m-1); reverse(arr, m, n-1); } 然后输出数组,注意行首尾不能有空格,数字间用空格隔。比如,可以用StringBuilder来构造输出字符串。 例如: StringBuilder sb = new StringBuilder(); for (int i=0; i <n; i++) { sb.append(arr[i]); if (i !=n-1) { sb.append(" "); } } System.out.println(sb.toString()); 这样应该可以满足输出要求。 但需要注意,当m是0的时候,不需要做任何反转,直接输出原数组即可。所以整个流程应该是: 处理m的值: m = m % n; if (m !=0) { reverse三次... } 然后输出数组。 测试样例: 输入样例: 6 2 1 2 3 4 5 6 处理后的m=2。 反转整个数组得到6 5 4 3 2 1。 反转前2个元素:5 6 4 3 2 1. 反转剩下的4个元素(索引2到5):5 6 1 2 3 4。正确。 另一个测试用例:当m=8,n=6时,m=8%6=2。处理方式同上,结果相同。 当m=0时,直接输出原数组。 那么,这样的代码应该可以满足要求。现在需要考虑代码的长度是否符合限制(16KB)。Java的代码长度可能比较长,特别是当处理输入输出的时候。比如,使用BufferedReader可能比Scanner更节省时间,但代码行数可能差不多。 编写完整的Java代码: 需要注意: - 类名必须为Main。 - 需要处理输入输出。 - 不要有额外的输出。 所以,完整的Java代码如下: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int n = Integer.parseInt(firstLine[0]); int m = Integer.parseInt(firstLine[1]); String[] parts = br.readLine().split(" "); int[] arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = Integer.parseInt(parts[i]); } m %= n; if (m != 0) { reverse(arr, 0, n - 1); reverse(arr, 0, m - 1); reverse(arr, m, n - 1); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { sb.append(arr[i]); if (i != n - 1) { sb.append(" "); } } System.out.println(sb); } private static void reverse(int[] arr, int start, int end) { while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } } } 这样的代码是否符合要求? 检查代码长度限制:16KB。Java的代码每个字符占1字节,所以16KB等于16384字节。这段代码应该足够简短,不会超过。 另外,时间限制400ms。使用三次反转的时间复杂度是O(n),对于n=1e5来说,三次反转的时间是可接受的。 内存方面,数组存储n个整数,每个int是4字节,所以数组大小是4*1e5=4e5字节,约400KB,这应该在64MB的限制内。 所以,这样的代码应该是正确的。</think>要高效实现线性表的循环右移,可以采用三次反转法。该方法的时间复杂度为 $O(n)$,空间复杂度为 $O(1)$,满足题目要求。 ### 方法思路 1. **处理移动次数**:当移动次数 $m$ 超过数组长度 $n$ 时,实际有效移动次数为 $m \mod n$。若 $m=0$,则无需移动。 2. **三次反转**: - **整体反转**:将整个数组反转。 - **前部反转**:反转前 $m$ 个元素- **后部反转**:反转剩余 $n-m$ 个元素。 ### 解决代码 ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int n = Integer.parseInt(firstLine[0]); int m = Integer.parseInt(firstLine[1]); String[] parts = br.readLine().split(" "); int[] arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = Integer.parseInt(parts[i]); } m %= n; if (m != 0) { reverse(arr, 0, n - 1); reverse(arr, 0, m - 1); reverse(arr, m, n - 1); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { sb.append(arr[i]); if (i != n - 1) { sb.append(" "); } } System.out.println(sb); } private static void reverse(int[] arr, int start, int end) { while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } } } ``` ### 代码解释 1. **输入处理**:使用 `BufferedReader` 读取输入数据,并解析成整数数组。 2. **有效移动次数**:通过取模运算得到实际移动次数 $m$。 3. **三次反转**: - `reverse(arr, 0, n-1)`:反转整个数组- `reverse(arr, 0, m-1)`:反转前 $m$ 个元素- `reverse(arr, m, n-1)`:反转剩余元素。 4. **结果输出**:使用 `StringBuilder` 拼接结果字符串,避免频繁字符串操作带来的性能问题。 该方法确保了时间和空间的高效性,适用于大规模数据。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值