数组排列问题求解

关于数组排列想说的话

最近应聘了不少公司,在某客网上网上笔试时遇到了一个排列的问题,没做出来,最后也没收到offer。于是就决定解决一下自己的知识漏洞。

三种类型

排列问题,面试中最常见的有3种类型:

  1. 全排列
  2. 下一个排列
  3. 第N个排列

1. 全排列问题

例如:leetcode第46题

给定一个没有重复数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

题目链接: https://leetcode-cn.com/problems/permutations/.
对于此类问题,排列的顺序不重要可以使用“交换”的思想回溯写出全排列。生成 N!个全排列需要时间O(N×N!)。该算法可以解决第一类问题。

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if(nums.empty()) return {};
        vector<vector<int>> res;
        backTrade(res, nums, 0);
        return res;
    }
    
    void backTrade( vector<vector<int>>& res, vector<int>& nums, int start)         {
        if(start == nums.size() - 1){
            res.push_back(nums);
            return;
        }
        for(int i=start; i<nums.size() ; i++){
            swap(nums[start], nums[i]);
            backTrade(res, nums, start+1);
            swap(nums[start], nums[i]);
        }
    }
};

但到这里还没完,Leetcode第47题立马就给出了变化

给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:[ [1,1,2], [1,2,1], [2,1,1] ]

题目链接: https://leetcode-cn.com/problems/permutations-ii/submissions/.
这样的话再使用46题的解法会产生重复项,只需要在恰当的地方去除重复项即可,代码如下:

class Solution {
public:  
    vector<vector<int>> permuteUnique(vector<int>& a) {
        vector<vector<int>> res;
        bt(a, res, 0);
        return res;
    }
    void bt(vector<int>& a, vector<vector<int>>& res, int level) {
        if (level == a.size()) {
            res.push_back(a);
            return;
        }
        //枚举的时候去重,只用没有排过的
        unordered_set<int> uniq;
        for (int i = level; i < a.size(); ++i) {
            if (uniq.count(a[i])) {
                continue; //already used
            }
            swap(a[i], a[level]);
            bt(a, res, level+1);
            swap(a[i], a[level]);
            uniq.insert(a[i]);
        }
    }
};

作者:Emilio
链接:https://leetcode-cn.com/problems/permutations-ii/solution/hui-su-mei-ju-shi-jian-dan-qu-zhong-ji-ke-by-emili/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.下一个排列

例如:leetcode第31题

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

题目链接: https://leetcode-cn.com/problems/next-permutation.

对这一类题目,可以使用D.E. Knuth 算法按照字典顺序生成全排列,在O(N) 时间内完成。
一遍扫描

首先,我们观察到对于任何给定序列的降序,没有可能的下一个更大的排列。
例如,以下数组不可能有下一个排列: [9, 5, 4, 3, 1]

我们需要从右边找到第一对两个连续的数字 a[i]a[i] 和 a[i-1]a[i−1],它们满足 a[i]>a[i-1]a[i]>a[i−1]。现在,没有对 a[i-1]a[i−1] 右侧的重新排列可以创建更大的排列,因为该子数组由数字按降序组成。因此,我们需要重新排列 a[i-1]a[i−1] 右边的数字,包括它自己。

现在,什么样的重新排列将产生下一个更大的数字?我们想要创建比当前更大的排列。因此,我们需要将数字 a[i-1]a[i−1] 替换为位于其右侧区域的数字中比它更大的数字,例如 a[j]a[j]。

我们交换数字 a[i-1]a[i−1] 和 a[j]a[j]。我们现在在索引 i-1i−1 处有正确的数字。 但目前的排列仍然不是我们正在寻找的排列。我们需要通过仅使用 a[i-1]a[i−1]右边的数字来形成最小的排列。 因此,我们需要放置那些按升序排列的数字,以获得最小的排列。

但是,请记住,在从右侧扫描数字时,我们只是继续递减索引直到我们找到 a[i]a[i] 和 a[i-1]a[i−1] 这对数。其中,a[i] > a[i-1]a[i]>a[i−1]。因此,a[i-1]a[i−1] 右边的所有数字都已按降序排序。此外,交换 a[i-1]a[i−1] 和 a[j]a[j] 并未改变该顺序。因此,我们只需要反转 a[i-1]a[i−1] 之后的数字,以获得下一个最小的字典排列。

代码如下:

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int flag = 0;
        int i = nums.size()-1;
        for( ; i>0 ;i--){
            if(nums[i-1] < nums[i]){
                flag = i-1;
                break;
            }
        }

        if(i==0){
            sort(nums.begin(), nums.end());
        }
        else{
            sort(nums.begin() + i, nums.end());
            int j = i;
            for(; j<nums.size();j++){
                if(nums[j] > nums[flag])
                    break;
            }
            int t = nums[j];
            nums[j] = nums[flag];
            nums[flag] = t;
        }
    }
};

3.第N个排列

虽然前两个算法很好使,但是前两个算法不能解决第三类问题,原因就在于:
1.良好的时间复杂度,即无回溯。
2.先前排列未知,即不能使用 D.E. Knuth 算法。

首先解决这个问题,取巧的方法确实有,就是C++的 next_permutation() 函数,具体用法可自行网上搜索。
用这个方法确实是因为这个方法太方便了。

class Solution {
public:
    string getPermutation(int n, int k) {
        vector<char> nums(n);
        for(int i=0; i<n; i++) 
            nums[i]=(char)(i+1+'0');
        while(k-->1) 
            next_permutation(nums.begin(), nums.end());
        string ans=""; 
        for(int i=0; i<n; i++) 
            ans+=nums[i];
        return ans;
    }
};

还有康拓展开

class Solution {
public:
	string getPermutation(int n, int k) {
		vector<char> chs={'1','2','3','4','5','6','7','8','9'};
		const int factor[]={1,1,2,6,24,120,720,5040,40320,362880};
		string str;
		for(--k; n--; k%=factor[n]){
			const int i=k/factor[n];
			str.push_back(chs[i]);
			chs.erase(chs.begin()+i);
		}
		return str;
	}
};

当然,递归理论上能解决此类问题,但是一般网站都不会过,因为要很好的剪枝才能优化的很好。先去睡了,有空再跟。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值