420. 火星人
人类终于登上了火星的土地并且见到了神秘的火星人。
人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。
这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。
火星人用一种非常简单的方式来表示数字——掰手指。
火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为1,2,3……
。
火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。
一个火星人用一个人类的手演示了如何用手指计数。
如果把五根手指——拇指、食指、中指、无名指和小指分别编号为1,2,3,4
和5
,当它们按正常顺序排列时,形成了5
位数12345
,当你交换无名指和小指的位置时,会形成5
位数12354
,当你把五个手指的顺序完全颠倒时,会形成54321
,在所有能够形成的120
个5
位数中,12345
最小,它表示1
;12354
第二小,它表示2
;54321
最大,它表示120
。
下表展示了只有3
根手指时能够形成的6
个3
位数和它们代表的数字:
三位数 123 132 213 231 312 321
代表的数字 1 2 3 4 5 6
现在你有幸成为了第一个和火星人交流的地球人。
一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。
你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。
输入数据保证这个结果不会超出火星人手指能表示的范围。
输入格式
输入包括三行,第一行有一个正整数$N$,表示火星人手指的数目。
第二行是一个正整数$M$,表示要加上去的小整数。
下一行是1
到$N$这$N$个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。
输出格式
输出只有一行,这一行含有$N$个整数,表示改变后的火星人手指的排列顺序。
每两个相邻的数中间用一个空格分开,不能有多余的空格。
数据范围
$1≤N≤10000$,
$1≤M≤100$
输入样例:
5
3
1 2 3 4 5
输出样例:
1 2 4 5 3
思路:
“下一个排列”的分析过程
1. 我们希望下一个数比当前数大,这样才满足“下一个排列”
的定义。因此只需要将后面的「大数」
与前面的「小数」
交换,就能得到一个更大的数。比如 123456
,将 5
和 6
交换就能得到一个更大的数 123465
。
2. 我们还希望下一个数增加的幅度尽可能的小,这样才满足“下一个排列”
与当前排列紧邻“的要求。为了满足这个要求,我们需要:
1. 在尽可能靠右的低位进行交换,需要从后向前查找。
2. 将一个 尽可能小的「大数」
与前面的「小数」
交换。比如 123465
,下一个排列应该把 5
和4
交换而不是把6
和 4
交换。
3. 将「大数」
换到前面后,需要将「大数」
后面的所有数重置为升序,升序排列就是最小的排列。以 123465
为例:首先按照上一步,交换 5
和 4
,得到123564
;然后需要将5
之后的数重置为升序,得到 123546
。显然 123546
比 123564
更小,123546
就是 123465
的下一个排列。
算法步骤
从后往前找,只要找到相邻两个数,前一个比后一个小就可以停止查找了,然后从后面的数中找到最小的大于找到的前一个数的数,交换,然后把后面的数排序即可。
例如12642
,由这五个数组成的且比他大的最小数是14226
1. 从后往前找到42
,前比后大,再找,64
,前比后大,再找,找到26
这两个数,后比前大,停止查找,因为此时则说明从此处开始往后的位数还没有从大到小排列,可以找到比他大的排列
2. 找到26
了,从6
往后包括6
的数中,找到比2
大的最小的数,是4
3. 交换2
和4
,得到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] + " ");
}
}
}