【LeetCode】下一个排列与全排列问题

(一)下一个排列

题目(Medium):31. 下一个排列

题目描述:

??实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

??如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

??必须原地修改,只允许使用额外常数空间。

??以下是一些例子,输入位于左侧列,其相应输出位于右侧列。

??1,2,3 → 1,3,2

??3,2,1 → 1,2,3

??1,1,5 → 1,5,1



解题思路

??本题有一个比较固定的算法思路,可以总结为以下5步:

??1、从后向前查找第一个相邻升序的元素对 (i,j),满足 A[i] < A[j]。此时 [j,end) 必然是降序

??2、在 [j,end) 从后向前查找第一个满足 A[i] < A[k]k。则 A[k]是在i之后刚好略大于 A[i]的数。

??3、将 A[i]A[k] 交换

??4、可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序

??5、如果在步骤 1 找不到符合的相邻元素对,说明当前 [begin,end) 为一个降序顺序,则直接跳到步骤 4

代码实现:

class Solution {
    public void nextPermutation(int[] nums) {
        //从后往前找到第一个非递增的数字,然后找到其后正好比其大的数字,二者交换,然后将其后的数字反转
        //时间复杂度O(n),空间O(1)
        int i=nums.length-2;
        while(i>=0 && nums[i]>=nums[i+1])
            i--;

        if(i<0){  //整体是降序的,直接反转
            reverse(nums,0,nums.length-1);
            return ;
        }

        //找到i之后刚好大于nums[i]的数字
        int j=nums.length-1;
        while(j>i && nums[j]<=nums[i])
            j--;

        swap(nums,i,j);

        reverse(nums,i+1,nums.length-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 begin,int end){
        int i=begin,j=end;
        while(i<j){
            swap(nums,i,j);
            i++;
            j--;
        }
    }
}

??注:可以发现上一篇博文中求下一个更大元素的第三题用的正是这一算法。

(二)全排列

题目(Medium):46. 全排列

题目描述:

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]

输出:

[

[1,2,3],

[1,3,2],

[2,1,3],

[2,3,1],

[3,1,2],

[3,2,1]

]



解题思路:回溯 或 交换

??**方法一:**回溯

??回溯是一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是 一个解的话(或者至少不是 最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即 回溯 并且再次尝试。

??这里我们是寻找由数组元素构成的所有排列,实际上相当于寻找所有的解,可以通过回溯来实现。利用递归每次向列表里添加一个数字,数字添加够以后再行回溯,向后添加新的解。

??**方法二:**交换递归

??要求所有数组元素构成的排列,可以看成两步:第一步:求所有可能出现在第一个位置的数字,即把第一个字符与后面的数字依次交换。第二步:固定一个数字,求后面所有数字的排列。

??很明显,这是典型的递归思路。

??两种方法比较,实际上可以发现方法二的效率更高。

代码实现:

//方法一:回溯添加
class Solution {
    List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        if(nums==null || nums.length==0)
            return res;
        backtrack(nums,new ArrayList<>());
        return res;
    }
    public void backtrack(int[] nums,List<Integer> temp){
        if(temp.size()==nums.length)
            res.add(new ArrayList<>(temp));
        else{
            for(int num:nums){
                if(temp.contains(num))
                    continue;
                temp.add(num);
                backtrack(nums,temp);  //递归添加下一个
                temp.remove(temp.size()-1); //回溯
            }
        }
    }
}
//方法二:交换递归
class Solution {
    List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        if(nums==null || nums.length==0)
            return res;
        permute(nums,0);
        return res;
    }
    public void permute(int[] nums,int begin){
        if(begin==nums.length-1){
            List<Integer> temp=new ArrayList<>();
            for(int num:nums)
                temp.add(num);
            res.add(temp);
        }else{
            for(int i=begin;i<nums.length;i++){
                swap(nums,begin,i);
                permute(nums,begin+1); //后面的递归
                swap(nums,begin,i);
            }
        }
    }

    public void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

(三)全排列(II)

题目(Medium):47. 全排列 II

题目描述:

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]

输出:

[

[1,1,2],

[1,2,1],

[2,1,1]

]



解题思路

??本题和上一题相比区别在于数组是否包含重复元素,我们可以采用效率较高的交换递归的方法,但是由于可能存在重复,在每次交换时,我们可以通过一个set记录已经交换过的元素,然后再交换时进行判断,如果该元素在set中,就代表已经交换过,没必要再交换,否则就会引起重复记录。

代码实现:

class Solution {
    List<List<Integer>> res=new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums==null || nums.length==0)
            return res;
        permuteUnique(nums,0);
        return res;
    }
    public void permuteUnique(int[] nums,int begin){
        if(begin==nums.length-1){
            List<Integer> temp=new ArrayList<>();
            for(int num:nums)
                temp.add(num);
            res.add(temp);
        }else{
            Set<Integer> set=new HashSet<>();
            for(int i=begin;i<nums.length;i++){
                if(set.contains(nums[i]))  //已经存在,跳过,不用交换
                    continue;
                set.add(nums[i]);
                swap(nums,begin,i);
                permuteUnique(nums,begin+1);
                swap(nums,begin,i);
            }
        }
    }

    public void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

注:求全排列的问题和剑指offer中【剑指Offer】27、字符串的排列?是相同的。

总结:

??本文记录了关于排列的三道算法题目,相对来说有一定的难度,特别是求下一个排列和全排列中交换递归的算法思想,可以把其作为一个相对固定的思路理解记忆,同时,我们也看到了回溯和递归思想在其中的应用。

原文地址:https://www.cnblogs.com/gzshan/p/12552863.html

时间: 2024-10-13 23:25:38

【LeetCode】下一个排列与全排列问题的相关文章

LeetCode 31 Next Permutation(下一个排列)

翻译 实现"下一个排列"函数,将排列中的数字重新排列成字典序中的下一个更大的排列. 如果这样的重新排列是不可能的,它必须重新排列为可能的最低顺序(即升序排序). 重排必须在原地,不分配额外的内存. 以下是一些示例,左侧是输入,右侧是输出: 1,2,3 → 1,3,2 3,2,1 → 1,2,3 1,1,5 → 1,5,1 原文 Implement next permutation, which rearranges numbers into the lexicographically

Leetcode:Next Permutation 下一个排列

Next Permutation: Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending ord

LeetCode:下一个排列【31】

LeetCode:下一个排列[31] 题目描述 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列. 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列). 必须原地修改,只允许使用额外常数空间. 以下是一些例子,输入位于左侧列,其相应输出位于右侧列.1,2,3 → 1,3,23,2,1 → 1,2,31,1,5 → 1,5,1 题目分析 什么样的排列将产生下一个更大的数字呢? 观察上面这个图,我们需要将数字 a[i-1]替换为位于其右侧区域的数

【LeetCode】下一个排列【找规律】

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列. 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列). 必须原地修改,只允许使用额外常数空间. 以下是一些例子,输入位于左侧列,其相应输出位于右侧列. 1,2,3 → 1,3,2 3,2,1 → 1,2,3 1,1,5 → 1,5,1 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/next-permutation 分析: 方法1:直接调

[C++]LeetCode: 79 Next Permutation (下一个排列,常见面试题)

题目: Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). The repla

LeetCode 31. 下一个排列(Next Permutation)

题目描述 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列. 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列). 必须原地修改,只允许使用额外常数空间. 以下是一些例子,输入位于左侧列,其相应输出位于右侧列. 1,2,3 → 1,3,2 3,2,1 → 1,2,3 1,1,5 → 1,5,1 解题思路 由于各个排列按照字典序排序,所以以 1,3,2 → 2,1,3为例,寻找下一个排列的步骤是: 首先找到从后往前第一个升序数对,在此例中即(1

[leetcode] 31. 下一个排列

31. 下一个排列 发现规律后很简单: 下一个排列即是要找字典序中下一个更大的排列. 如串s:1 2 3 6 5 4 2 的下一个排列是 1 2 4 2 3 5 6 我们将数字串头点即为a,尾点记作b,从右往左看找到第一个降序数字的位置记为p,如例: 1236542 a_p___b 在s[p+1~b]子串中找到>s[p]的最小点,然后与s[p]交换: 1246532 a_p___b 此时,s[p+1~b]肯定仍然满足降序排列(从右往左看是升序),然后将s[p+1~b]做下reverse即可 12

【LeetCode每天一题】Next Permutation(下一个排列)

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). The replaceme

leetcode 31下一个排列

/** 验证一般情况(元素数目大于等于3)有几个情况分析:两个特殊情况: 6 5 4 3 2 1 完全反序,这种序列没有下一个排序,因此重新排序1 2 3 4 5 6 1 2 3 4 5 6 完全升序,很容易看出翻转5 6得到下一个排序: 因此对于以下一般情况有: 1 2 6 5 4 3 找到右边第一个a[i]<a[i+1]的元素2,再从右往左找出2的下一个元素3,交换2,3,之后对2右边元素reverse: 1 2 5 4 6 3 从右往左找到第一个a[i]<a[i+1]的元素4,从右往左找