【leetcode】下一个重排列

面试题目:

给定任意一个自然数,获取它重新排列后,下一个比它大的自然数,要求时间复杂度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]。

解题思路:

下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。具体地:

  1. 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
  2. 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。

具体地,我们这样描述该算法,对于长度为 n 的排列 a:

  1. 首先从后向前查找第一个顺序对 (i,i+1),满足 a[i]<a[i+1]。这样「较小数」即为 a[i]。此时 [i+1,n)必然是下降序列。
  2. 如果找到了顺序对,那么在区间 [i+1,n)中从后向前查找第一个元素 j 满足a[i]<a[j]。这样「较大数」即为 a[j]
  3. 交换 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("不存在");
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考:

下一个排列:官方题解

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值