尋找全排列的下一個數(shù)
摘自漫畫算法:
題目:給出一個正整數(shù),找出這個正整數(shù)所有數(shù)字全排列的下一個樹。說的通俗點就是在一個整數(shù)所包含數(shù)字的全部組合中,找到一個大于且僅大于原數(shù)的新整數(shù)。
例子:
- 如果輸入12345,則返回12354
- 如果輸入12354,則返回12435
- 如果輸入12435,則返回12453
解題思路
在給出具體思路解法之前,先思考一個問題:由固定幾個數(shù)字組成的整數(shù),怎么排列最大?怎么排列最小?
解答:如果是固定的幾個數(shù)字,在逆序排列的情況下值最大,在順序排列的情況下值最小
例子:
給出1、2、3、4、5這幾個數(shù)字。最大組合為54321,最小組合為12345。
給出整數(shù)12354,它包含的數(shù)字是1、2、3、4、5,如何找到這些數(shù)字全排列之后僅大于原數(shù)的新整數(shù)呢?
為了和原數(shù)接近,我們需要盡量保持高位不變,低位在最小的范圍內(nèi)變換順序。至于變換順序的范圍大小,則取決于當(dāng)前整數(shù)的逆序區(qū)域。

如圖所示,12354的逆序區(qū)域是最后兩位,僅看這兩位已經(jīng)是當(dāng)前的最大組合。若想最接近原數(shù),又比原數(shù)更大,必須從倒數(shù)第3位開始改變。
怎樣改變呢?12345的倒數(shù)第3位是3,我們需要從后面的逆序區(qū)域中找到大于3的最小的數(shù)字,讓其和3的位置進(jìn)行互換。
如圖:

互換后的臨時結(jié)果是12453,倒數(shù)第3位已經(jīng)確定,這個時候最后兩位仍然是逆序狀態(tài)。我們需要把最后兩位轉(zhuǎn)變?yōu)轫樞驙顟B(tài),以此保證在倒數(shù)第3位數(shù)值為4的情況下,后兩位盡可能小。

這樣一來,就得到了想要的結(jié)果12435。
總結(jié):以上思路看起來復(fù)雜,其實只要3個步驟:
- 從后向前查看逆序區(qū)域,找到逆序區(qū)域的前一位,也就是數(shù)字置換的邊界。
- 讓逆序區(qū)域的前一位和逆序區(qū)域中大于它的最小數(shù)字交換位置。
- 把原來的逆序區(qū)域轉(zhuǎn)為順序狀態(tài)。
這種解法有一個“高大上”的名字:字典序算法。
代碼實現(xiàn)
import java.util.Arrays;
/**
* 描述:尋找全排列的下一個樹。
* <p>
* Create By ZhangBiao
* 2020/6/7
*/
public class RangeNextNumber {
public static int[] findNearestNumber(int[] numbers) {
// 1、從后向前查看逆序區(qū)域,找到逆序區(qū)域的前一位,也就是數(shù)字置換的邊界
int index = findTransferPoint(numbers);
// 如果數(shù)字置換邊界是0,說明整個數(shù)組已經(jīng)逆序,無法得到更大的相同數(shù)字組成整數(shù),返回null
if (index == 0) {
return null;
}
// 2、把逆序區(qū)域的前一位和逆序區(qū)域中剛剛大于它的數(shù)字交換位置
// 復(fù)制并入?yún)?,避免直接修改入?yún)? int[] numbersCopy = Arrays.copyOf(numbers, numbers.length);
exchangeHead(numbersCopy, index);
// 3、把原來的逆序區(qū)域轉(zhuǎn)為順序
reverse(numbersCopy, index);
return numbersCopy;
}
private static int[] reverse(int[] num, int index) {
for (int i = index, j = num.length - 1; i < j; i++, j--) {
int temp = num[i];
num[i] = num[j];
num[j] = temp;
}
return num;
}
private static int[] exchangeHead(int[] numbers, int index) {
int head = numbers[index - 1];
for (int i = numbers.length - 1; i > 0; i--) {
if (head < numbers[i]) {
numbers[index - 1] = numbers[i];
numbers[i] = head;
break;
}
}
return numbers;
}
private static int findTransferPoint(int[] numbers) {
for (int i = numbers.length - 1; i > 0; i--) {
if (numbers[i] > numbers[i - 1]) {
return i;
}
}
return 0;
}
private static void outputNumbers(int[] numbers) {
for (int i : numbers) {
System.out.print(i);
}
System.out.println();
}
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
// 打印12345之后的10個全排列整數(shù)
for (int i = 0; i < 19; i++) {
numbers = findNearestNumber(numbers);
outputNumbers(numbers);
}
}
}