尋找全排列的下一個(gè)數(shù)
摘自漫畫算法:
題目:給出一個(gè)正整數(shù)落塑,找出這個(gè)正整數(shù)所有數(shù)字全排列的下一個(gè)樹纽疟。說的通俗點(diǎn)就是在一個(gè)整數(shù)所包含數(shù)字的全部組合中,找到一個(gè)大于且僅大于原數(shù)的新整數(shù)憾赁。
例子:
- 如果輸入12345污朽,則返回12354
- 如果輸入12354,則返回12435
- 如果輸入12435龙考,則返回12453
解題思路
在給出具體思路解法之前蟆肆,先思考一個(gè)問題:由固定幾個(gè)數(shù)字組成的整數(shù),怎么排列最大晦款?怎么排列最醒坠Α?
解答:如果是固定的幾個(gè)數(shù)字柬赐,在逆序排列的情況下值最大亡问,在順序排列的情況下值最小
例子:
給出1、2肛宋、3州藕、4、5這幾個(gè)數(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)行互換。
如圖:
互換后的臨時(shí)結(jié)果是12453缩功,倒數(shù)第3位已經(jīng)確定晴及,這個(gè)時(shí)候最后兩位仍然是逆序狀態(tài)。我們需要把最后兩位轉(zhuǎn)變?yōu)轫樞驙顟B(tài)嫡锌,以此保證在倒數(shù)第3位數(shù)值為4的情況下虑稼,后兩位盡可能小。
這樣一來势木,就得到了想要的結(jié)果12435蛛倦。
總結(jié):以上思路看起來復(fù)雜,其實(shí)只要3個(gè)步驟:
- 從后向前查看逆序區(qū)域啦桌,找到逆序區(qū)域的前一位溯壶,也就是數(shù)字置換的邊界。
- 讓逆序區(qū)域的前一位和逆序區(qū)域中大于它的最小數(shù)字交換位置甫男。
- 把原來的逆序區(qū)域轉(zhuǎn)為順序狀態(tài)且改。
這種解法有一個(gè)“高大上”的名字:字典序算法。
代碼實(shí)現(xiàn)
import java.util.Arrays;
/**
* 描述:尋找全排列的下一個(gè)樹板驳。
* <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慨蓝,說明整個(gè)數(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個(gè)全排列整數(shù)
for (int i = 0; i < 19; i++) {
numbers = findNearestNumber(numbers);
outputNumbers(numbers);
}
}
}