1. 程式人生 > 其它 >PAT (Basic Level) Practice1008 陣列元素迴圈右移問題

PAT (Basic Level) Practice1008 陣列元素迴圈右移問題

技術標籤:演算法# PTA演算法c++陣列

1008 陣列元素迴圈右移問題

一、題目

一個數組A中存有 N ( > 0 ) N(>0) N(>0)個整數,在不允許使用另外陣列的前
提下,將每個整數迴圈向右移 M ( ≥ 0 ) M(≥0) M(0)個位置,即將A中的資料由 ( A 0 A 1 . . . A N − 1 ) (A_0 A_1...A_{N-1}) (A0A1...AN1)變換為 ( A N − M . . . A N − 1 A 0 A 1 . . . A N − M 1 ) (A_{N-M}...A_{N-1}A_0A_1...A_{N-M_1}) (ANM..

.AN1A0A1...ANM1)(最後M個數迴圈移至最前面的M個位置)。如果需要考慮程式移動資料的次數儘量少,要如何設計移動的方法?

二、輸入輸出

輸入格式

每個輸入包含一個測試用例,第1行輸入 N ( 1 ≤ N ≤ 100 ) N(1≤N≤100) N1N100 M ( ≥ 0 ) M(≥0) M0;第2行輸入N個整數,之間用空格分隔。

輸出格式

在一行中輸出迴圈右移M位以後的整數序列,之間用空格分隔,序列結尾不能有多餘空格。

三、樣例

輸入樣例

6 2
1 2 3 4 5 6

輸出樣例

5 6 1 2 3 4

四、題目分析

1.只輸出不改變陣列

如果只從完成題目的角度講,不需要移動陣列中的元素

,只需要輸出時從陣列倒數第M個元素迴圈輸出到陣列末尾,再從陣列起始位置輸出到倒數第M-1個元素。(程式碼①)

2.按照題目要求:不使用額外陣列,交換次數儘可能少

  • 方法一:(程式碼②)
    首先以長度為12的陣列為例,(選取長度為12的原因是12的因數較多,便於討論)
    為了方便討論,假設陣列元素值和其下標相同:
    在這裡插入圖片描述
    如果需要時間複雜度最小,每個元素要直接移動到最終位置。因此,只需要一個變數swap來暫存中間被覆蓋的元素值。
    在這裡插入圖片描述

    • 情形一:陣列右移5位:

      • step1:swap儲存元素5
        在這裡插入圖片描述
      • step2:5被0元素覆蓋
        在這裡插入圖片描述
      • step3:0號下標儲存swap的值 在這裡插入圖片描述
      • step4:swap儲存10;
      • step5:10被0號下標的5覆蓋;
      • 最終完成移動,且0號下標的值正好是最後一次交換的值。

      由此看來,一次迴圈即可完成右移嗎?

    • 情形二:陣列右移三位
      根據情形一總結出的方法進行右移:

      發現交換隻發生在幾個元素之中
      在這裡插入圖片描述

    • 情形三:陣列右移9位
      根據情形一總結出的方法進行右移:

      交換依舊發生在幾個元素之中,且步長位3
      在這裡插入圖片描述

    • 綜合以上情形:
      一次迴圈變換元素的分佈和M,N的最大公因數有關,因此完成整個陣列的右移,需要將最大公因數分成的幾個陣列分別右移:
      在這裡插入圖片描述
      需要兩層迴圈實現,內層迴圈移動一組元素,外層迴圈遍歷所有被分成的組。

    • 此演算法的時間複雜度是理論的最小值 O ( n ) O(n) O(n),使用一個swap臨時空間,空間複雜度位 O ( 1 ) O(1) O(1)

  • 方法二:

    以長度為12的陣列為例,向右移動6位,期望的輸出如下圖所示:即從6輸出到陣列末尾,再從陣列起始位置輸出完整個陣列
    在這裡插入圖片描述
    經過變換的陣列最終排列應該為:

    67891011012345

    從初始序列到期望序列可以經過逆序變換得到:

    • step1:轉置從陣列倒數第M個元素迴圈輸出到陣列末尾的元素;
      在這裡插入圖片描述

    • step2:轉置從陣列起始位置到倒數第M-1個數組元素;

    在這裡插入圖片描述

    • step3:轉置整個陣列。
      在這裡插入圖片描述

    因此呼叫三次轉置函式即可完成陣列右移。

  • 方法四:std::rotate

五、程式碼

  • 程式碼①:

    #include <bits/stdc++.h>
    using namespace std;
    int main()
    {
        int n;
        int m;
        cin >> n >> m;
        int *num = new int[n];
        m = m % n;//m>n的移動和m%n等效
        for (int i = 0; i < n; i++)//迴圈讀入數值
            cin >> num[i];
        int flag = 0;//控制輸出格式變數,使第一個輸出前無空格
        for (int i = n - m; i < n; i++)
        {
            if (flag)
                cout << ' ';
            flag = 1;
            cout << num[i];
        }//從陣列倒數第M個元素迴圈輸出到陣列末尾
        for (int i = 0; i < n - m; i++)
        {
            if (flag)
                cout << ' ';
            flag = 1;
            cout << num[i];
        }//從陣列起始位置輸出到倒數第M-1個元素
    
        return 0;
    }
    
  • 程式碼②:

    #include <bits/stdc++.h>
    using namespace std;
    inline int Max_common_factor(int a, int b)//求最大公因數
    {
        int c = a % b;
        while (c)
        {
            a = b;
            b = c;
            c = a % b;
        }
        return b;
    }
    int main()
    {
        int n;
        int m;
        int swap;//用於交換的空間
        int max_common_factor;//最大公因數
        cin >> n >> m;
        m = m % n;//m>n的移動和m%n等效
        int *num = new int[n];
        for (int i = 0; i < n; i++)//迴圈讀入數值
            cin >> num[i];
        if (m)//當m不為0時需要進行移動,否則直接輸出即可
        {
            max_common_factor = Max_common_factor(n, m);//最大公因數
            for (int i = 0; i < max_common_factor; i++)//外層迴圈的次數是最大公因數的值
            {
                int k = i;
                swap = num[i];//交換空間儲存第一個元素,第一個元素用來暫存被交換的值
                for (int j = 0; j < n / max_common_factor; j++)
                {
                    k = (k + m) % n;
                    num[i] = num[k];
                    num[k] = swap;
                    swap = num[i];
                }
            }
        }
        int flag = 0;
        for (int i = 0; i < n; i++)
        {
            if (flag)
                cout << ' ';
            flag = 1;
            cout << num[i];
        }//按照格式輸出陣列
    
        return 0;
    }
    

六、總結

  • 求最大公因數函式:

    int Max_common_factor(int a, int b)
    {
        int c = a % b;
        while (c)
        {
            a = b;
            b = c;
            c = a % b;
        }
        return b;
    }
    
  • 行內函數:

    • 作用:減少函式呼叫的開銷編譯器處理對行內函數的呼叫語句時,是將整個函式的程式碼插入到呼叫語句處,而不會產生呼叫函式的語句。
    • 呼叫行內函數之前要有函式的定義,不能只有宣告
    • 定義格式:
      inline 函式名(/*引數列表*/{
      	/*函式體*/;
      };