LeetCode四道Permutations问题详解

LeetCode中与Permutations相关的共有四题: 

  31. Next Permutation 
  46. Permutations 
  47. Permutations II 
  60. Permutation Sequence 
  大致包括了所有全排列问题可能考到的题型。 
  本文按序列出了解这四道题的详细思路和AC代码。在各题之间,尽可能地使用了不同的解法,使大家对各种方法能有个了解。

目录


下一个全排列数


题一描述:

  原题链接:31. Next Permutation 
  给定任一非空正整数序列,生成这些数所能排列出的下一个较大序列。若给出的序列为最大序列,则生成最小序列。

输入 → 输出
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
题一解析:
概念:

  这里,先考虑一个序列的最大最小情况。当一个序列为非递减序列时,它必然是该组数的最小的排列数;同理,当一个序列为非递增序列时,它必然是该组数的最大的排列数。

举例:

  那么给定一个p(n)要如何才能生成p(n+1)呢?先来看下面的例子: 
  我们用

结论:

  由此,我们可以知道,本题的关键即是求出数组末尾的最长的非递增子序列。 
  不妨假设在数组nums中,nums[k+1]…nums[n]均满足前一个元素大于等于后一个元素,即这一子序列非递增。 
  那么,我们要做的,就是把nums[k]与其后序列中稍大于nums[k]的数交换,接着再逆序nums[k+1]…nums[n]即可。 
   
  根据这个思路,可以得到如下的AC代码。

题一C++解答:
        int index = nums.size()-1;
	while (nums[index]<=nums[index-1]) {
		index--;
	}
	if (index == 0) {
		sort(nums.begin(), nums.end());
	}
	for (int i = nums.size() - 1; i >= index; i--)
		if (nums[i] > nums[index-1]) {
			swap(nums[i], nums[index-1]);
			break;
		}
	sort(nums.begin() + index, nums.end());

无重复数字的全排列数


题二描述:

  原题链接:46. Permutations 
  给定一个无重复数字的序列,返回这些数所能排列出所有序列。

样例输入:
[1,2,3]

样例输出:
[   
    [1,2,3],
    [1,3,2],
    [2,1,3],
    [2,3,1],
    [3,1,2],
    [3,2,1]
]
题二解析:

  这是很经典的全排列问题,本题的解法很多。因为这里的所有数都是相异的,故笔者采用了交换元素+DFS的方法来求解。 
  下面列出我的AC代码。代码中附有中文注释,在此就不再赘述具体步骤。

题二Java解答:
public class Solution {

    // 最终返回的结果集
    List<List<Integer>> res = new ArrayList<List<Integer>>();

    public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        if (len==0||nums==null)  return res;

        // 采用前后元素交换的办法,dfs解题
        exchange(nums, 0, len);
        return res;
    }

    public void exchange(int[] nums, int i, int len) {
        // 将当前数组加到结果集中
        if(i==len-1) {
            List<Integer> list = new ArrayList<>();
            for (int j=0; j<len; j++){
                list.add(nums[j]);
            }
            res.add(list);
            return ;
        }
        // 将当前位置的数跟后面的数交换,并搜索解
        for (int j=i; j<len; j++) {
            swap(nums, i, j);
            exchange(nums, i+1, len);
            swap(nums, i, j);
        }
    }

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

有重复数字的全排列数


题三描述:

  原题链接:47. Permutations II 
  给定一个含有重复数字的序列,返回这些数所能排列出的所有不同的序列。

样例输入:
[1,1,2]

样例输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]
题三解析
题意:

  本题与题二略有不同,给定的序列中含有重复元素,需要返回这些数所能排列出的所有不同的序列集合。

思路:

  这里我们先考虑一下,它与第二题唯一的不同在于:在DFS函数中,做循环遍历时,如果与当前元素相同的一个元素已经被取用过,则要跳过所有值相同的元素。 
  举个例子:对于序列<1,1,2,3>。在DFS首遍历时,1 作为首元素被加到list中,并进行后续元素的添加;那么,当DFS跑完第一个分支,遍历到1 (第二个)时,这个1 不再作为首元素添加到list中,因为1 作为首元素的情况已经在第一个分支中考虑过了。 
  为了实现这一剪枝思路,有了如下的解题算法。

解题算法:

  1. 先对给定的序列nums进行排序,使得大小相同的元素排在一起。 
  2. 新建一个used数组,大小与nums相同,用来标记在本次DFS读取中,位置i的元素是否已经被添加到list中了。 
  3. 根据思路可知,我们选择跳过一个数,当且仅当这个数与前一个数相等,并且前一个数未被添加到list中。 
  根据以上算法,对题二的代码略做修改,可以得到如下的AC代码。 
  (在处理一般性问题时,建议用此算法,毕竟题二只是特殊情况)

题三C++解答
class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int> &nums){
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        res.push_back(nums);
        while(permute(nums)){
            res.push_back(nums);
        }
        return res;        
    } 
private:
    bool permute(vector<int> &nums){
        int index = nums.size()-1;
        while(index>=1 && nums[index]<=nums[index-1])
            index--;
        if(index<=0)
            return false;
        for(int i=nums.size()-1;i>=index;i--){
            if(nums[i]>nums[index-1]){
                swap(nums[i], nums[index-1]);
                break;
            }
        }
        sort(nums.begin()+index, nums.end());
        return true;
    }
 

取特定位置的全排列字符串


题四描述:

  原题链接:60. Permutation Sequence 
  给定正整数n和k,要求返回在[1,2,…,n]所有的全排列中,第k大的字符串序列。

样例输入:
3  4

样例输出:
"231"
题四解析:
思路:

  这里我们先考虑一个特殊情况,当n=4时,序列为[1,2,3,4],有以下几种情况: 
  “1+(2,3,4)的全排列” 
  “2+(1,3,4)的全排列” 
  “3+(1,2,4)的全排列” 
  “4+(1,2,3)的全排列” 
  我们已经知道,对于n个数的全排列,有n!种情况。所以,3个数的全排列就有6种情况。 
   
  如果我们这里给定的k为14,那么它将会出现在: 
    “3+(1,2,4)的全排列” 
  这一情况中。

  我们可以程式化地得到这个结果:取k=13(从0开始计数),(n-1)!=3!=6,k/(n-1)!=2,而3在有序序列[1,2,3,4]中的索引就是2。 
  同理,我们继续计算,新的k=13%6=1,新的n=3,那么1/(n-1)!=2/2=0。在序列[1,2,4]中,索引0的数是1。那么,此时的字符串为”31”。 
  继续迭代,新的k=1%2=1,新的n=2,那么k/(n-1)!=1/1=1。在序列[2,4]中,索引为1的数是4。那么,此时的字符串为”314”。最后在串尾添上仅剩的2,可以得到字符串”3142”。 
  经过验算,此串确实是序列[1,2,3,4]的全排列数中第14大的序列。

解题算法:

  1. 创建一个长度为n 的数组array,存放对应下标n的阶乘值。 
  2. 再新建一个长度为n 的数组nums,初始值为nums[i]=i+1,用来存放待选的字符序列。 
  3. 将得到的k减1后,开始迭代。迭代的规则是:迭代n次,每次选nums数组中下标为k/(n-1)!的数放在字符串的末尾,新的k=k%(n-1)!,新的n=n-1。 
  4. 最后,返回得到的字符串。 
  根据以上算法,可以得到如下的AC代码。

题四C++解答:
class Solution {
public:
    string getPermutation(int n, int k) {
        string dict="123456789", res;
        vector<int> fun(n, 1);
        for(int i=1;i<n;i++)
            fun[i] = i*fun[i-1];
        k = k-1;
        for(int i=n;i>=1;i--){
            int tmp = k / fun[i-1];
            k = k % fun[i-1];
            res.push_back(dict[tmp]);
            dict.erase(tmp, 1);
        }
        return res;
    }
};
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值