有重复项数字的全排列

331 阅读1分钟

描述

给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。

数据范围: 0<n≤80<n≤8 ,数组中的值满足 −1≤val≤5−1≤val≤5

要求:空间复杂度 O(n!)O(n!),时间复杂度 O(n!)O(n!)

示例1

输入:

[1,1,2]

返回值:

[[1,1,2],[1,2,1],[2,1,1]]

示例2

输入:

[0,1]

返回值:

[[0,1],[1,0]]

(暴力回溯):

回想一下高中所学的排列组合知识:有3个红球,2个白球,1个黑球,请问有多少中排列方式。

我们的计算方式是:一个有6个位置,先选择3个位置放红球,再从剩下的3个位置中选择2个位置放白球,最后剩下一个位置放黑球。总共的排列数有种。

我们这题就是要用代码生成这些排列。

我们从左往右依次填入给定的数字,每个字符使用一次。

当我们使用完了所有的数字,那么就可以将生成的数组加入到最终结果中。
当我们没有使用完数字,考虑下一步应该使用什么字符呢?之前使用过的数字不能再使用了。我们可以对使用过的数字做一个标记,表示已经使用过了。然后使用没有标记过的数字。

依据之前提到放球的解答,我们应该把不同的字符做一个统计,然后把他们放到一些位置中。我们使用map<char,int>m给不同的字符做标记。如果字符i使用了一次。m[i]--。相应的当m[i]==0时,表示没有字符i可供使用。

算法实现如下:

    if (tmp.size() == n) {
        ret.push_back(tmp);//当最后生成的tmp长度等于n即可添加到最后结果中,n指的是输入数组长度,
    }
    for (auto &i:m) {
        if (i.second > 0) {//当i.second>0,表示有数字i.first可以被使用
            i.second--;//使用一次数字i.first,i.second--;
            tmp .push_back(i.first);//加到生成的数组中
            backtrack(m, ret, tmp, n);//下一步生成tmp
            tmp.pop_back();
            i.second++;//上面回溯结束后,使用过的i.first拿出去,又可以被使用,因此tmp弹出最后一个元素,i.second++
        }
    }
}

vector<vector<int> > permuteUnique(vector<int> &num) {
    map<int, int> m;
    for (auto& i : num) {
        m[i]++;
    }
    vector<vector<int>> ret;
    vector<int> tmp;
    backtrack(m, ret, tmp, num.size());
    return ret;
}