面试题目:
给定任意一个自然数,获取它重新排列后,下一个比它大的自然数,要求时间复杂度O(n)。
例如: 给定1233, 它的下一个是1323; 给定1323, 它的下一个是1332。
这道题直接全排列,暴力搜索时间复杂度太高,应该怎么入手?这是字典序算法
。
首先看一道leetcode的相似题目:
leetcode题目
题目链接:31. 下一个排列
下一个排列是指其整数的下一个字典序更大的排列。如果存在下一个字典序更大的排列,输出更大,不存在,则输出最小的。
以数字序列[1,2,3] 为例,其排列按照字典序依次为:
[1,2,3]
[1,3,2]
[2,1,3]
[2,3,1]
[3,1,2]
[3,2,1]
这样,排列 [2,3,1] 的下一个排列即为 [3,1,2]。特别的,最大的排列 [3,2,1] 的下一个排列为最小的排列 [1,2,3]。
解题思路:
下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。具体地:
- 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
- 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。
具体地,我们这样描述该算法,对于长度为 n 的排列 a:
- 首先从后向前查找第一个顺序对
(i,i+1)
,满足a[i]<a[i+1]
。这样「较小数」即为a[i]
。此时[i+1,n)
必然是下降序列。 - 如果找到了顺序对,那么在区间
[i+1,n)
中从后向前查找第一个元素 j 满足a[i]<a[j]
。这样「较大数」即为a[j]
。 - 交换
a[i]
与a[j]
,此时可以证明区间[i+1,n)
必为降序。我们可以直接使用双指针反转区间[i+1,n)
使其变为升序,而无需对该区间进行排序。
图片来自:https://leetcode-cn.com/problems/next-permutation/solution/xia-yi-ge-pai-lie-by-leetcode-solution/
如果在步骤 1 找不到顺序对,说明当前序列已经是一个降序序列,即最大的序列,我们直接跳过步骤 2 执行步骤 3,即可得到最小的升序序列。
Java代码实现:
方法一:指针+sort排序
从最后一个开始往前找比自己小的,交换位置,然后对之后的元素进行升序排序。
class Solution {
public void nextPermutation(int[] nums) {
if (nums.length <= 1) return;
for (int i = nums.length - 1; i > 0; i--) { // 从后往前遍历
if (nums[i] > nums[i - 1]) {
int index = i;
while (index < nums.length && nums[index] > nums[i - 1]) {
index++; //index会停在第一个小于nums[0]的地方
}
int tmp = nums[index - 1]; //交换i-1和index-1,然后对从i开始的剩余元素进行排序
nums[index - 1] = nums[i - 1];
nums[i - 1] = tmp;
Arrays.sort(nums, i, nums.length);
return;
}
}
//如果数组是降序排列的
Arrays.sort(nums);
}
}
方法二:两遍扫描
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = nums.length - 1;
while (j >= 0 && nums[i] >= nums[j]) {
j--;
}
swap(nums, i, j);
}
reverse(nums, i + 1);
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums, int start) {
int left = start, right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
}
回归原始题目:给定任意一个自然数,获取它重新排列后,下一个比它大的自然数。相当于把自然数的每一位拆成数组进行的全排列,找下一个比它大的全排列对应的自然数。
这里把整个自然数当字符串处理:
String[] arr = in.nextLine().split("");
int[] nums = new int[arr.length];
for (int i = 0; i < nums.length; i++) {
nums[i] = Integer.parseInt(arr[i]);
}
还可以当数字处理:
ArrayList<Integer> nums = new ArrayList<>();
while ((num % 10) > 0) {
int tmp = num % 10;
num = num / 10;
nums.add(0, tmp);
}
import java.util.*;
public class nextNumber {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] arr = in.nextLine().split("");
int[] nums = new int[arr.length];
for (int i = 0; i < nums.length; i++) {
nums[i] = Integer.parseInt(arr[i]);
}
for (int i = nums.length - 1; i > 0; i--) { // 从后往前遍历
if (nums[i] > nums[i - 1]) {
int index = i;
while (index < nums.length && nums[index] > nums[i - 1]) {
index++; // index会停在第一个小于nums[0]的地方
}
int tmp = nums[index - 1]; // 交换i-1和index-1,然后对从i开始的剩余元素进行排序
nums[index - 1] = nums[i - 1];
nums[i - 1] = tmp;
Arrays.sort(nums, i, nums.length);
String res = "";
for (int n : nums) {
res += n;
}
System.out.println(res);
return;
}
}
// 如果本身就是降序排或者长度小于等于1
System.out.println("不存在");
}
}
参考: