您的位置:首页 > Web前端

剑指offer-字符串的排列

2017-07-13 18:21 295 查看
此文,我希望对排列类题目做一个汇总,我也不只是做剑指offer的题目,也会结合leetcode的题目进行讲解。

问题1(全排列)

题目:[Permutations]

思路

这个题的基本思路有两个,一个是枚举每一个位置的元素去做dfs,另外一个是这样:考虑递归的方法,每一个递归是这样生成的,1.从当前位置开始的每一个元素试探当前位置,然后结合后面元素构成的全排列而成。

试探操作可以用交换操作代替。

代码

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

permute( nums, sz, 0, ret );
return ret;
}
private:
void permute( vector<int>& arr, int n, int depth, vector<vector<int>>& ret ){
if( depth == n ){
ret.push_back(arr);
}else{
for(int i = depth; i < n; ++i){ // i = depth,从当前层的元素开始试探,之前的元素都试探过了,不能再次试探
swap( arr[depth], arr[i] );
permute( arr, n, depth + 1, ret );
swap( arr[depth], arr[i] );

4000
}
}
}
private:
void swap(int& a, int& b){
int t = a;
a = b;
b = t;
}
};


问题2(没有重复的全排列)

思路

主要是考虑重复的情形:

比如[1,2,2]

第一种情形:交换元素和试探位置元素一样。比如,对于第二个位置的2,自身试探一次,后面的2还要试探一次,由于这个试探元素和交换元素一样,试探会造成重复。[1, 2_1, 2_2], [1, 2_2, 2_1]。所以,不行。

第二种情形:交换元素和试探位置不一样,但是和它之前的某个元素一样,这样也会导致对试探元素的重复试探。因为除去试探元素和交换元素,剩下的所有元素都一样。所以,也会重复。比如,[1,2,2],第二个位置的2对1进行试探得到:[2,1,2],然后有, [2, 2, 1]。那么,现在考虑第三个位置的2对1进行试探,[2,2,1], [2, 1, 2],这组序列上面已经得到过了,所以不行。

结论:交换元素不能和包括试探元素到交换元素这之间序列的任何一个元素相同,否则导致上面的两种情形出现。

代码

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

permute( nums, sz, 0, ret );
return ret;
}
private:
void permute( vector<int>& arr, int n, int depth, vector<vector<int>>& ret ){
if(depth == n) ret.push_back(arr);
else{

for(int i = depth; i < n; ++i){
if( is_repeat( arr, depth, i ) ){
swap( arr[depth], arr[i] );
permute( arr, n, depth+1, ret );
swap( arr[depth], arr[i] );
}

}
}
}
bool is_repeat(vector<int>& arr, int depth, int i){
for(int idx = depth; idx < i; ++idx){ // [depth, i) should not be same as i
if( arr[idx] == arr[i] )
return false;
}
return true;
}
void swap(int& a, int& b){
int t  = a;
a = b;
b = t;
}
};


思路2(非递归-stl)

代码

class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ret;

sort(nums.begin(), nums.end()); // 一定要排序
do{
ret.push_back(nums);
}while(std::next_permutation(nums.begin(), nums.end()));

return ret;
}
};


思路3(非递归-自己实现)

从我另一篇博客里直接干过来的[ leetcode-31-NextPermutation]

,

下面是自己的实现,求下一个字典序列。算法步骤如下:

从后向前寻找一个升序对(nums[i],nums[i+1]),如果不存在,则已经是降序,reverse返回即可。

如果存在升序对,从后向前寻找第一个比nums[i]大元素,交换。

对从i+1到结尾的元素,置逆

解释一下上面的思路,首先要获得最小字典序,应该从后向前进行调整。因为后面数字的权重小。其次,需要从后寻找升序对,如果不存在升序对,证明已经全部降序,其实已经是最后一个字典序了。再者,假设升序对是( i, i+1 ),既然升序对起点在i,那么证明i之后的元素都是降序,因为后面是不存在升序对的,否则反证法与升序对起点位于i矛盾。

下面要进行调整,原则是要尽可能获得“较小”的升序对,即下一个序列和当前序列的差应该是最小的。所以,应该从后向前找到第一个比nums[i]大的元素,小肯定是不行的,这样没法保证你是上升的。找到第一个的目的是,由于i之后的元素是降序,所以第一个比nums[i]大就能保证是”较小”的上升序列。然后交换nums[i]和之后的这个元素

最后,元素交换完毕之后,i之后的序列任然是降序的,但是为了获得”较小”的上升序列,需要将后面的元素变成升序,只需reverse即可。这三步操作,都是为了寻找最小的seh

代码

class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> ret;
std::sort(nums.begin(), nums.end());

do{
ret.push_back(nums);
}while( next_permutation(nums) );
return ret;
}
private:
bool next_permutation( vector<int>& nums ){
int sz = nums.size();
int i = 0;

// find the ascend order
for( i = sz - 2; i >= 0; --i){
if( nums[i] < nums[i+1] )
break;
}
if(-1==i){
std::reverse( nums.begin(), nums.end() );
return false;
}

// swap
int j = 0;
for(j = sz-1; j > i; --j){
if( nums[j] > nums[i] )
break;
}
swap( nums[i], nums[j] );

// reverse
std::reverse( nums.begin() + i + 1, nums.end() );
return true;
}
private:
void swap(int& a, int& b){
int t = a;
a = b;
b = t;
}
};


问题3

题目:[字符串的排列]

思路

同上。

代码

class Solution {
public:
vector<string> Permutation(string str) {

vector<string> ret;
if( str == "" )
return ret;
sort(str.begin(), str.end());
do{
ret.push_back(str);
}while( next_permutation(str.begin(), str.end()) );

return ret;
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: