PAT1008 数组循环右移问题(非暴力输出解法)

原题地址https://www.patest.cn/contests/pat-b-practise/1008

这道题很早之前就用暴力输出AC过了,可是本题解法众多,这里介绍两种方法。第一种方法来源于网上的博客,非常有趣。第二种方法也不是本人原创,不敢夺人之功。

第一种:翻转法

这是一种很有技巧性的解法,经过三次翻转即可得到结果。我们设数组长度为N,右移位数为M。

1. 首先将区间(以0位首位), 【N- M,N - 1】进行翻转。

2.再将区间【0,(N - M - 1)】进行对称翻转。

3.最后将整个区间进行对称翻转。

比如:N = 8, M = 3,数组为1 2 3 4 5 6 7 8

        第一步,将 6 7 8三个数进行翻转, 数组变为1 2 3 4 5 8 7 6

        第二步,将1 2 3 4 5 五个数进行对称翻转,数组变为 5 4 3 2 1 8 7 6

        第三步,将整个数组进行对称翻转,数组变为6 7 8 1 2 3 4 5

放一波C艹实现


    #include <cstdio>
    const int MAXN = 110;
    void reverse(int a[], int left, int right)
    {
        int temp; //用于接收临时变量
        for(int i = left;i <= (left + right)/2 ;i++) // 中心翻转
        {
            temp = a[i];
            a[i] = a[right + left - i];
            a[right + left -i] = temp;
        }
    }
    
    int main()
    {
        int N, M, iInput[MAXN];
        while(~scanf("%d %d", &N, &M))
        {
            for(int i = 0;i < N;i++)
            {
                scanf("%d", &iInput[i]); // 输入
            }
            
            M %= N; //M有大于N的情况
            
            reverse(iInput, N - M, N - 1);
            reverse(iInput, 0, N - M - 1);
            reverse(iInput, 0, N - 1);
            
            for(int i = 0;i < N;i++)
            {
                printf("%d%c", iInput[i], (i != N - 1)? ' ':'\n');
            }
        }
        return 0;

    }

上述代码的时间复杂度为O(n),还不错。


第二种:循环法

这种方法来源于《算法笔记:上机训练实战指南》,使用了一种比较特殊的算法。下面介绍一下实现方法。

以N = 6,M = 2,数组为1 2 3 4 5 6为例。

还是以0为首位,将第(N - M)位的数字拿出,保存在temp变量中,这时数组变为

                                                1 2 3 4 _ 6

然后,将应该结果中放在(N - M)位置的数组,本例中即为 3 和第( N - M)位进行交换,此时,数组变为

                                                1 2 _ 4 3 6

然后继续这一过程,

                                                _ 2 1 4 3 6

这时,下一次将空缺位左移会回到初始位置,所以不再移动,然后将temp变量填入空缺位,数组变为

                                                5 2 1 4 3 6

然后,再把第(N - M + 1)位保存到temp,继续这一过程,直到第(N - 1)位完成。

但是这种方法有缺陷,各位可以手动模拟一下,当N = 8,M = 3,这时其实当第一次循环完成后数组就已经是正确答案了,继续循环反而会导致错误。

针对这个问题,书中也给出了解决方法。设d为N 和 M的最大公约数,那么只要从N - M位开始循环,直到N - M + d - 1位结束就可以。

下面又是一波C艹实现

    #include <cstdio>
    const int MAXN = 110;
    
    int gcd(int a,int b) // 求最大公约数
    {
        if(b == 0)    return a;
        else    return gcd(b, a%b);
    }

    int main()
    {
        int N, M, iInput[MAXN];
        while(~scanf("%d%d",&N, &M))
        {
            for(int i = 0;i < N;i++)
            {
                scanf("%d",&iInput[i]);
            }
            
            M %= N;
            
            if(M != 0)
            {
                int d = gcd(N, M);
                for(int i = N - M;i <= N - M + d - 1;i++)
                {
                    int temp = iInput[i]; //记录temp变量;
                    int pos = i, next; // pos为此时此刻正在处理的变量,即为空缺位, next为将要和空缺位进行交换的位置
                    do
                    {
                        next = (pos - M + N) % N; //计算将要进行交换的位置 ,加N是如果移动到数组小于M的位置时再从右边开始
                        
                        if(next == i)  //如果回到了初始位置
                             iInput[pos] = temp;
                        else
                            iInput[pos] = iInput[next]; //如果下一个位置不是初始位置,那么将下一个位置的数字填入空缺位
                            
                        pos = next; // 继续下一个位置
                     } while(pos != i);
                    
                }
            }
            
            for(int i = 0;i < N;i++)
            {
                printf("%d%c",iInput[i], (i != N - 1)?' ':'\n');
            }
        }
        return 0;

    }


以上两种方法都已经AC,可放心食用。

(第一篇博客,若有不妥之处希望各位读者多多指教)



猜你喜欢

转载自blog.csdn.net/aldo101/article/details/79516499