1. 程式人生 > 其它 >day20 420 火星人 (排列)

day20 420 火星人 (排列)

420. 火星人

人類終於登上了火星的土地並且見到了神祕的火星人。

人類和火星人都無法理解對方的語言,但是我們的科學家發明了一種用數字交流的方法。

這種交流方法是這樣的,首先,火星人把一個非常大的數字告訴人類科學家,科學家**這個數字的含義後,再把一個很小的數字加到這個大數上面,把結果告訴火星人,作為人類的回答。

火星人用一種非常簡單的方式來表示數字——掰手指。

火星人只有一隻手,但這隻手上有成千上萬的手指,這些手指排成一列,分別編號為1,2,3……

火星人的任意兩根手指都能隨意交換位置,他們就是通過這方法計數的。

一個火星人用一個人類的手演示瞭如何用手指計數。

如果把五根手指——拇指、食指、中指、無名指和小指分別編號為1,2,3,4

5,當它們按正常順序排列時,形成了5位數12345,當你交換無名指和小指的位置時,會形成5位數12354,當你把五個手指的順序完全顛倒時,會形成54321,在所有能夠形成的1205位數中,12345最小,它表示112354第二小,它表示254321最大,它表示120

下表展示了只有3根手指時能夠形成的63位數和它們代表的數字:

三位數 123 132 213 231 312 321

代表的數字 1 2 3 4 5 6

現在你有幸成為了第一個和火星人交流的地球人。

一個火星人會讓你看他的手指,科學家會告訴你要加上去的很小的數。

你的任務是,把火星人用手指表示的數與科學家告訴你的數相加,並根據相加的結果改變火星人手指的排列順序。

輸入資料保證這個結果不會超出火星人手指能表示的範圍。

輸入格式
輸入包括三行,第一行有一個正整數 N N N,表示火星人手指的數目。

第二行是一個正整數 M M M,表示要加上去的小整數。

下一行是1 N N N N N N個整數的一個排列,用空格隔開,表示火星人手指的排列順序。

輸出格式
輸出只有一行,這一行含有 N N N個整數,表示改變後的火星人手指的排列順序。

每兩個相鄰的數中間用一個空格分開,不能有多餘的空格。

資料範圍
1 ≤ N ≤ 10000 1≤N≤10000 1N10000,
1 ≤ M ≤ 100 1≤M≤100 1M100
輸入樣例:

5
3
1 2 3 4 5

輸出樣例:

1 2 4 5 3

思路:

可參考 LeetCode 31.下一個排列

“下一個排列”的分析過程

  1. 我們希望下一個數比當前數大,這樣才滿足“下一個排列”的定義。因此只需要將後面的「大數」與前面的「小數」交換,就能得到一個更大的數。比如 123456,將 56 交換就能得到一個更大的數 123465
  2. 我們還希望下一個數增加的幅度儘可能的小,這樣才滿足“下一個排列”與當前排列緊鄰“的要求。為了滿足這個要求,我們需要:
    1. 在儘可能靠右的低位進行交換,需要從後向前查詢。
    2. 將一個 儘可能小的「大數」 與前面的「小數」交換。比如 123465,下一個排列應該把 54 交換而不是把64交換。
    3. 「大數」換到前面後,需要將「大數」後面的所有數重置為升序,升序排列就是最小的排列。以 123465 為例:首先按照上一步,交換 54,得到123564;然後需要將5 之後的數重置為升序,得到 123546。顯然 123546123564 更小,123546 就是 123465 的下一個排列。

演算法步驟

從後往前找,只要找到相鄰兩個數,前一個比後一個小就可以停止查找了,然後從後面的數中找到最小的大於找到的前一個數的數,交換,然後把後面的數排序即可。

例如12642,由這五個陣列成的且比他大的最小數是14226

  1. 從後往前找到42,前比後大,再找,64,前比後大,再找,找到26這兩個數,後比前大,停止查詢,因為此時則說明從此處開始往後的位數還沒有從大到小排列,可以找到比他大的排列
  2. 找到26了,從6往後包括6的數中,找到比2大的最小的數,是4
  3. 交換24,得到14622,然後把4後面的數排序,得到14226,即結果。

Java程式碼

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[] arr = new int[n];
        for(int i = 0;i < n;i++){
            arr[i] = scanner.nextInt();
        }

        //加上m後的排列,實際就是找到火星人原始排列的後面第m個排列,故我們找m次即可
        while(m-- != 0){
            int index = -1;
           for(int i = arr.length - 2;i >= 0;i--)
               if(arr[i] < arr[i+1]){
                   index = i;
                   break;
               }
           if(index == -1){
               /*本題中已經指明輸入資料保證最後結果不會超出火星人手指能表示的範圍,所以其實這裡的if不會執行的,
               如,654321,它已經值六位數的最大排列,並無下一個排列了,此時才會出現index == -1的情況,這時我們
               就需要在該if中做點相應的處理,但是題目指明瞭加上m後的排列都會在範圍內,所以不會出現index == -1
               的情況,故一定進else塊,即該if針對該題不需要,但是作為注意事項,我寫上了*/
           }else{
               //由於這兩個數後面的數肯定是降序排好的,因為我們開始找就是找到第一對逆序的
               //所以現在找大於index位置的數,卻又是後面最小的數,我們可以直接找到第一個不大於index位置的數即可
               //那麼這個數的前一個肯定是大於index位置的數的
               int lgidx = index + 1;
               while(lgidx < arr.length && arr[index] < arr[lgidx] ) lgidx ++;

               //交換
               int temp = arr[index];
               arr[index] = arr[lgidx -1];
               arr[lgidx - 1] = temp;

               //index之後的排序,注意是從index + 1開始
               Arrays.sort(arr,index + 1,arr.length);
           }

        }
        //輸出最終結果
        for(int i = 0;i < arr.length;i++){
            System.out.print(arr[i] + " ");
        }
    }
}

在這裡插入圖片描述