[C++]LeetCode: 115 Permutations (求一组数的全排列)

题目:

Given a collection of 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].

Answer 1: 树 (避免标记一个数字是否被使用)

思路:我们来观察一下问题,是求解一组数字的全排列。我们来建立一颗全排列的树,

[C++]LeetCode: 115 Permutations (求一组数的全排列)_第1张图片

对于第k层节点(不算顶层1234),就是交换固定了前面的k-1位,然后分别swap(k, k), swap(k, k+1),swap(k, k+2)...

例如上图中的第二层,我们固定了第一位(即2),然后分别交换第1,1位,1, 2位,1, 3位。

这样建立一棵树,我们总是能得到所有的全排列组合。

这种方法,我们可以避免维护一个used数组,来标记一个数字是否被使用。

Attention:

1. 在第k层,我们交换了一次后,比如得到2314,递归求得2314的子树的所有可能后,需要重新reset成2134,再交换得到2431,再递归求解所有排列。一定不能忘记reset.

<span style="font-size:14px;">//reset num
swap(num[begin], num[i]);</span>
2. swap一定从swap(k, k)开始,虽然并没有交换,但是我们依然需要求这个情况下的所有子树排列。如2134

swap(num[begin], num[i]);

AC Code:

class Solution {
public:
    vector<vector<int> > permute(vector<int> &num) {
        vector<vector<int> > ret;
        if(num.size() == 0) return ret;
        permute_helper(num, 0, ret);
        return ret;
    }

private:
    //排列num[begin...end], num[0..begin-1]已经被固定
    void permute_helper(vector<int>& num, int begin, vector<vector<int>>& ret)
    {
        if(begin >= num.size())
        {
            ret.push_back(num);
            return;
        }
        
        for(int i = begin; i < num.size(); i++)
        {
            swap(num[begin], num[i]);
            permute_helper(num, begin + 1, ret);
            //reset num
            swap(num[begin], num[i]);
        }
    }
};


Answer 2: 递归法

思路:这道题也是一个NP问题。依然是以前的方法,用一个循环递归处理子问题。区别是这里并不是一直往后推进的,我们前面的数可以放在后面,为了避免重复使用,我们需要维护一个used数组来表示该元素是否已经在当前结果中,判断后,我们每次取一个元素放入结果,然后递归剩下的元素,这样就不会出现重复元素。

唯一需要注意的是,我们需要“保护现场”。递归函数的勤勉,我们分别设置了used[i]的标记,标明该元素被使用,并且把该元素加入到了当前结果中,而在递归函数后,我们需要把该元素从结果移除,并把该标记重置为false. 递归函数必须保证在进入和离开函数的时候,变量的状态是一样的,这样才能保证正确性。这是NP问题,经常会用到的一个技巧,应该记住。

Attention:

1. 删除vector数组最后一个元素。vector.end()-1. 注意保护现场

tmp.push_back(num[i]);
used[i] = true;
permute_helper(num, used, tmp, ret);
tmp.erase(tmp.end()-1);
used[i] = false;

AC Code:

class Solution {
public:
    vector<vector<int> > permute(vector<int> &num) {
        vector<vector<int> > ret;
        if(num.size() == 0) return ret;
        vector<int> tmp;
        vector<bool> used(num.size(), false);
        permute_helper(num, used, tmp, ret);
        return ret;
    }

private:
    void permute_helper(vector<int>& num, vector<bool> used, vector<int> tmp, vector<vector<int>>& ret)
    {
        if(tmp.size() == num.size())
        {
            ret.push_back(tmp);
            return;
        }
        
        for(int i = 0; i < num.size(); i++)
        {
            if(!used[i])
            {
                tmp.push_back(num[i]);
                used[i] = true;
                permute_helper(num, used, tmp, ret);
                tmp.erase(tmp.end()-1);
                used[i] = false;
            }
        }
        return;
    }
};

Answer 3: 迭代法

思路:我们来看下如何用迭代的方法处理这个问题。迭代一般要理清每次新加入一个元素,我们应该做些什么。这里,假设我们已经有了当前前i 个元素的所有排列的集合。当加入第i+1个元素时,我们应该要将这个元素代入每一个之前的结果,并且放到每一个结果的各个位置上,从0到i共i+1个位置。因为之前的结果是没有重复的,我们这道题假设元素集合没有重复,所以新加入元素后也不会有重复。

复杂度:第二层循环对于ret进行遍历,ret以平方量级增长,所以总的时间复杂度也是指数量级以上的。

Attention:

1.在数组相应位置插入元素,前面数组和后面位置要一致。

item.insert(item.begin()+k, num[i]);
error: item.insert(cur.begin()+k, num[i]);

AC Code:

class Solution {
public:
    vector<vector<int> > permute(vector<int> &num) {
        vector<vector<int> > ret;
        if(num.size() == 0) return ret;
        
        vector<int> first;
        first.push_back(num[0]);
        ret.push_back(first);
        
        //依次添加元素num[i]
        for(int i = 1; i < num.size(); i++)
        {
            vector<vector<int> > newRet;
            //处理上一个结果集合
            for(int j = 0; j < ret.size(); j++)
            {
                vector<int> cur(ret[j]);
                for(int k = 0; k < cur.size()+1; k++)
                {
                    vector<int> item(cur);
                    item.insert(item.begin()+k, num[i]);
                    newRet.push_back(item);
                }
            }
            ret = newRet;
        }
        
        return ret;
    }
};

我们已经不止一次遇到这种NP问题,这种递归的求解办法也是比较常见的。类似有N皇后问题,Sudoku Solver, Combination Sum, Combination Sum II, Combination, 解决思路都差不多,掌握好解题套路。这道题还有个扩展,如果元素集合有重复,做完再来回顾一下。Permutation II.

你可能感兴趣的:(LeetCode,backtracking,NP-Problem)