题目:
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: 树 (避免标记一个数字是否被使用)
思路:我们来观察一下问题,是求解一组数字的全排列。我们来建立一颗全排列的树,
对于第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]);
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;
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; } };
思路:我们来看下如何用迭代的方法处理这个问题。迭代一般要理清每次新加入一个元素,我们应该做些什么。这里,假设我们已经有了当前前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; } };