LeetCode 46/47. Permutations i, ii

本文介绍了解决46题(不重复数字的全排列)和47题(包含重复数字的全排列)的方法。针对46题,采用基于交换的回溯算法;而47题则在46题的基础上增加了对重复元素的处理,通过排序和跳过重复元素确保结果唯一。

1. 题目描述

46
Given a collection of distinct numbers, return all possible permutations.

For example,
[1,2,3] have the following permutations:
[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1].

47
Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,
[1,1,2] have the following unique permutations:
[1,1,2], [1,2,1], and [2,1,1].

2. 解题思路

首先对于元素各异的情况,使用基于交换的回溯方法可以很容易解决
然后是有重复元素的情况,对于这种情况, 我们首先想到的是按照元素各异的情况处理, 然后通过排序删除重复部分, 然而, 当我们实践的时候, 得到了TLE。
这样, 我们就不得不仔细思考, 应该加入的元素的实际情况。
1. 如果两个相互需要交换的元素是相同的值的话, 就可以直接pass了, 否则, 这个序列会被加入两次
2. 从起始元素到当前元素, 如果元素的值发生重复的话, 我们也应该pass
- 这点理解起来有些不是那么直观(举个栗子)
这里写图片描述

3. code

3.1 46

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        if (nums.size() == 0)
            return res;

        helper(nums, res, 0);
        return res;
    }

private:
    void helper(vector<int>& nums, vector<vector<int>> & res, int depth){
        if (depth == nums.size()){
            res.push_back(nums);
            return;
        }

        for (int i = depth; i < nums.size(); i++){
            swap(nums[i], nums[depth]);
            helper(nums, res, depth + 1);
            swap(nums[i], nums[depth]);
        }
    }
};

3.2 47

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        if (nums.size() == 0)
            return res;

        sort(nums.begin(), nums.end());
        helper(nums, res, 0);
        //sort(res.begin(), res.end());
        //res.erase(unique(res.begin(), res.end()), res.end());
        return res;
    }

private:
    void helper(vector<int>& nums, vector<vector<int>> & res, int depth){
        if (depth == nums.size()){
            res.push_back(nums);
            return;
        }

        for (int i = depth; i < nums.size(); i++){
            if ((i != depth && nums[i] == nums[depth]))
                continue;

            // 找重复出现元素    
            bool flag = false;
            for (int j = depth; j != i; j++){
                if (nums[j] == nums[i]){
                    flag = true;
                    break;
                }
            }

            if (flag)
                continue;

            swap(nums[i], nums[depth]);
            helper(nums, res, depth + 1);
            swap(nums[i], nums[depth]);
        }
    }
};

4. 大神解法

4.1 47 demo1

这个解法, 说实话, 到现在还是理解起来比较困难, 尤其是recursion 操作 之后, 没有swap 操作, 感觉很神奇~~

class Solution {
public:
    void recursion(vector<int> num, int i, int j, vector<vector<int> > &res) {
        if (i == j-1) {
            res.push_back(num);
            return;
        }
        for (int k = i; k < j; k++) {
            if (i != k && num[i] == num[k]) continue;
            swap(num[i], num[k]);
            recursion(num, i+1, j, res);
        }
    }
    vector<vector<int> > permuteUnique(vector<int> &num) {
        sort(num.begin(), num.end());
        vector<vector<int> >res;
        recursion(num, 0, num.size(), res);
        return res;
    }
};

4.2 next_permutations

这种解法, 巧妙的将permuation 转化为 next_permuation 进行处理, brilliant !!!

/*
yes,you are right.Actually,it is a problem similar to "Next Permutation", which rearranges numbers into the lexicographically next greater permutation of numbers. the difference between these two problems lies in how permutations we are required to generate.sorry,my English level is limited,hope this might help.
*/
  bool nextPermutation(vector<int> &num) {
        int len=num.size()-1;
        for(int pos=len-1;pos>=0;--pos){
            if(num[pos]<num[pos+1]){
                int smallNum=num[pos];
                int lastPosBiggerThanSmallNum=
                    len-(find_if(num.rbegin(),
                    num.rend(),(bind2nd(greater<int>(),smallNum)))-     
                    num.rbegin());
                swap(num[pos],num[lastPosBiggerThanSmallNum]);
                sort(num.begin()+pos+1,num.end());
                return true;
            }
        }
        return false;
    }
    vector<vector<int> > permuteUnique(vector<int> &num){
        vector<vector<int> > res;
        if(num.empty()) 
            return res;
        sort(num.begin(),num.end());
        res.push_back(num);
        while(nextPermutation(num)){
            res.push_back(num);
        }
        return res;
    }

4.3 DFS + map

借助 map, 巧妙的化解重复元素的危机

I see most solutions are using next permutation. That's great and only uses O(1) space.

Anyway I am sharing backtracking solution which uses O(n) space. This is actually a typical backtracking problem. We can use hash map to check whether the element was already taken. However, we could get TLE if we check vector num every time. So we iterate the hash map instead.

class Solution {
public:
vector<vector<int> > permuteUnique(vector<int> &num) {
    vector<vector<int>> v;
    vector<int> r;
    map<int, int> map;
    for (int i : num)
    {
        if (map.find(i) == map.end()) map[i] = 0;
        map[i]++;
    }
    permuteUnique(v, r, map, num.size());
    return v;
}

void permuteUnique(vector<vector<int>> &v, vector<int> &r, map<int, int> &map, int n)
{
    if (n <= 0)
    {
        v.push_back(r);
        return;
    }
    for (auto &p : map)
    {
        if (p.second <= 0) continue;
        p.second--;
        r.push_back(p.first);
        permuteUnique(v, r, map, n - 1);
        r.pop_back();
        p.second++;
    }
}
};

4.4 DFS + flag

使用一个 bool 标记, 标记当前元素值是否已经被处理过, 如果处理过了, 下次遇到相同的值之后, 可以直接pass

/*
Use an extra boolean array " boolean[] used" to indicate whether the value is added to list.

Sort the array "int[] nums" to make sure we can skip the same value.

when a number has the same value with its previous, we can use this number only if his previous is used
*/
public class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if(nums==null || nums.length==0) return res;
        boolean[] used = new boolean[nums.length];
        List<Integer> list = new ArrayList<Integer>();
        Arrays.sort(nums);
        dfs(nums, used, list, res);
        return res;
    }

    public void dfs(int[] nums, boolean[] used, List<Integer> list, List<List<Integer>> res){
        if(list.size()==nums.length){
            res.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(used[i]) continue;
            if(i>0 &&nums[i-1]==nums[i] && !used[i-1]) continue;
            used[i]=true;
            list.add(nums[i]);
            dfs(nums,used,list,res);
            used[i]=false;
            list.remove(list.size()-1);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值